R-Version: [Default] [64-bit] C:\Program Files\R\R-4.1.0

Imports

Die Daten haben wir von folgender Quelle bezogen und werden auch auf dieser genauer Beschrieben: https://sorry.vse.cz/~berka/challenge/PAST/index.html (linke Seite PKDD’99 Challenge > Data > Financial Data Description)

Einlesen der Daten

Der Datensatz besteht aus acht verschiedenen Tabellen, welche teils durch Keys miteinander verknüpft sind.

root_path <- "./xselling_banking_data-1/xselling_banking_data/"

accounts <- read.csv(paste0(root_path, "account.csv"), header = TRUE, sep = ";")
cards <- read.csv(paste0(root_path, "card.csv"), header = TRUE, sep = ";")
clients <- read.csv(paste0(root_path, "client.csv"), header = TRUE, sep = ";")
dispositions <- read.csv(paste0(root_path, "disp.csv"), header = TRUE, sep = ";")
districts <- read.csv(paste0(root_path, "district.csv"), sep = ";")
loans <- read.csv(paste0(root_path, "loan.csv"), header = TRUE, sep = ";")
orders <- read.csv(paste0(root_path, "order.csv"), header = TRUE, sep = ";")
transactions <- read.csv(paste0(root_path, "trans.csv"), header = TRUE, sep = ";")

Cleaning

Accounts

sample_n(accounts, 5)

Die Account-Tabelle enthält vier Kolonnen: die Account-ID, die District-ID (welche auf die District-Tabelle verweist), die Frequenz, welche die Häufigkeit der Ausstellung der Abrechnungen als Kategorie besagt, und das Erstellungsdatum des Accounts. Die Frequenz kann eine von drei verschiedenen Werten annehmen.

unique(accounts$frequency)
[1] "POPLATEK MESICNE"   "POPLATEK PO OBRATU" "POPLATEK TYDNE"    

Nachfolgend sollen die Frequenz-Werte übersetzt und das Datum in ein richtiges Format transformiert werden. Ausserdem soll die Tabelle auf fehlende Werte überprüft werden.

accounts$date <- as.Date(as.character(accounts$date), format= "%y%m%d")

accounts$frequency[accounts$frequency == "POPLATEK MESICNE"]   <- "monthly"
accounts$frequency[accounts$frequency == "POPLATEK TYDNE"]     <- "weekly"
accounts$frequency[accounts$frequency == "POPLATEK PO OBRATU"] <- "after_transaction"

sum(is.na(accounts))
[1] 0

Es gibt also keine fehlende Werte in diesem Dataframe.

Cards

sample_n(cards, 5)

Die Card-Tabelle enthält die Kolonnen Card-ID, Disp-ID (welche auf die Dispositon-Tabelle verweist), den Typ der Karte und das Ausstellungsdatum. Auch hier muss das Datum umgewandelt werden. Der zeitliche Teil wird ignoriert, da er immer 0 ist. Weiter werden Gold-Karten zu normalen Karten umgewandelt und Junior-Karten entfernt, da unser Modell das Kaufen einer normalen Karte vorhersagen soll. Auch hier werden wieder die fehlenden Werte geprüft.

cards$issued <- as.Date(as.character(cards$issued), format= "%y%m%d")

cards$type[cards$type == "gold"]     <- "classic"
cards <- filter(cards, type == "classic")

sum(is.na(cards))
[1] 0

Es gibt keine fehlende Werte, welche weitere Aktionen erfordern würden.

Clients

sample_n(clients, 10)

Die Tabelle Clients enthält die Client-ID, das Geburtsdatum und die District-ID (welche auf die District-Tabelle verweist). Der Spalte Geburtsdatum sieht man auf den ersten Blick die Datumsräpresentation nicht an. In der Doku wird aber die Struktur ersichtlich: Das Datumsformat ist für Männer YYMMDD und für Frauen YYMMDD+50DD.

In Folge wird die Nummer in ihre Datumsräpresentation konvertiert und die Spalte “gender” als male/female aufgeschlüsselt. Zudem wird das Alter der Kunden im Jahr 1999 berechnet, da der Datensatz aus diesem Jahr stammt. Anschliessend kann das Geburtsdatum entfernt werden.

Ausserdem werden auch hier wieder die fehlenden Werte überprüft.

# Months above 12 must be female
clients <- mutate(clients, gender = 
                     ifelse(substr(birth_number, 3, 4) > 12, "female", "male"))

# Substract the 50 to get the birth month
clients <- mutate(clients, birth_month =
                     ifelse(as.numeric(substr(birth_number, 3, 4)) > 12,
                            as.numeric(substr(birth_number, 3, 4)) - 50,
                            as.numeric(substr(birth_number, 3, 4))))

# Transform the birth_number to a date
clients <- mutate(clients, birth_number = paste("19",
                                                substr(birth_number, 1, 2), 
                                                str_pad(birth_month, 2,
                                                        pad = "0"),
                                                substr(birth_number, 5, 6),
                   sep = "", collapse = NULL))
clients$birth_date <- as.Date(as.character(clients$birth_number),
                               format= "%Y%m%d")

# Remove unused columns
clients$birth_month <- NULL
clients$birth_number <- NULL

# Get the age of the clients in the year 1999 and save it in a column
get_age <- function(birth_date) {
  base_year <- 99
  year <- substr(birth_date, 3, 4)
  result <- base_year - as.integer(year)
  
  return(result)
}
clients <- clients %>%
   mutate(age = get_age(birth_date))

clients$birth_date <- NULL

sum(is.na(clients))
[1] 0

Auch hier gibt es keine fehlenden Werte, welche untersucht werden müssten.

Jugendliche und Personen, welche während des Zeitraums des Datensatzes erst erwachsen worden sind, sollen nicht in die Auswertung einfliessen. Da sich der Datensatz über einen Zeitraum von sechs Jahren erstreckt werden alle Clients jünger als 25 Jahre herausgefiltert.

clients <- clients %>% filter(age >= 25)

Dispositions

sample_n(dispositions, 5)

Die Tabelle Dispostions enthält die Disposition-ID, die Client-ID (welche auf die Tabelle Clients verweist), die Account-ID (welche auf die Tabelle Accounts verweist) und den Typ der Disposition. Disposition steht dabei für die Rechte eines Kunden für ein gewisses Konto. Hier sollen nur Owners verwendet werden, da die Analyse nur Eigentümer von Konten behandeln soll. Da der Type dann für jede Dispostion der gleiche ist, kann diese Variabel entfernt werden.

dispositions <- dispositions %>% filter(type == 'OWNER')
dispositions$type <- NULL

sum(is.na(dispositions))
[1] 0

Auch hier gibt es keine fehlenden Werte, welche genauer untersucht werden müssten.

Districts

sample_n(districts, 5)

Die Tabelle Districts enthält demographische Informationen über verschiedene Gebiete. Die Spaltennamen sind hier nur nummeriert und müssen richtig benannt werden. Dies können wir anhand der Doku machen.

districts <- rename(districts, district_id = A1, district_name = A2, region = A3, 
                   inhabitants = A4, municipalities_inhabitants_smaller_499 = A5, 
                   municipalities_inhabitants_500_to_1999 = A6, 
                   municipalities_inhabitants_2000_to_9999 = A7, 
                   municipalities_inhabitants_larger_10000 = A8, cities = A9, 
                   urban_inhabitants_ratio = A10, average_salary = A11,
                   unemployment_rate_95 = A12, unemployment_rate_96 = A13,
                   entrepreneurs_per_1000 = A14, crimes_95 = A15,
                   crimes_96 = A16)

sum(is.na(districts))
[1] 0

Auch hier gibt es keine fehlenden Werte.

Transactions

sample_n(transactions, 5)

Die Transaktions-Tabelle enthält die Kolonnen Transaktions-ID, Account-ID (welche auf die Tabelle Accounts verweist), Date (das Datum der Transaktion), Type (den Typ der Transaktion), Operation und k_symbol (weitere kategorische Informationen über die Transaktion), Amount (der absolute Wert der Transaktion), Balance (der neue Kontostand) und Informationen über die Bank und den Account.

In den Transaktionen muss das Datum gemäss Format YYMMDD konvertiert und die verschiedenen tschechischen Ausdrücke übersetzt werden. “VYDAJ” heisst übersetzt Ausgabe, “VYBER” Entnahme. Wir übersetzen hier beide Begriffe mit dem Wert “withdrawal”.

# Change formats
transactions$date <- as.Date(as.character(transactions$date), format= "%y%m%d")
transactions$amount <- as.numeric(transactions$amount)
transactions$balance <- as.numeric(transactions$balance)

# Translate values
transactions$type[transactions$type == "PRIJEM"] <- "income"
transactions$type[transactions$type == "VYDAJ"]  <- "withdrawal"
transactions$type[transactions$type == "VYBER"]  <- "withdrawal"

transactions$operation[transactions$operation == "VKLAD"]          <- "cash credit"
transactions$operation[transactions$operation == "PREVOD Z UCTU"]  <- "collection"
transactions$operation[transactions$operation == "VYBER"]          <- "cash withdrawal"
transactions$operation[transactions$operation == "PREVOD NA UCET"] <- "remittance"
transactions$operation[transactions$operation == "VYBER KARTOU"]   <- "card withdrawal"

transactions$k_symbol[transactions$k_symbol == "DUCHOD"] <- "pension"
transactions$k_symbol[transactions$k_symbol == "UROK"] <- "interest"
transactions$k_symbol[transactions$k_symbol == "SIPO"] <- "household"
transactions$k_symbol[transactions$k_symbol == "SLUZBY"] <- "payment statement"
transactions$k_symbol[transactions$k_symbol == "POJISTNE"] <- "insurance"
transactions$k_symbol[transactions$k_symbol == "SANKC. UROK"]  <- "neg_interest"
transactions$k_symbol[transactions$k_symbol == "UVER"]  <- "loan_pay"

sum(is.na(transactions))
[1] 760931

In dieser Spalte gibt es eine grosse Anzahl fehlender Werte. Bereits im Sample ist ersichtlich, dass bei den Kolonnen bank, k_symbol und account fehlende Werte als NA oder als leerer string vorkommen. Der Anteil fehlender Werte soll als nächstes untersucht werden.

missing_bank_percentage = round(sum(transactions$bank == '') * 100 / dim(transactions)[1], 2)
missing_k_symbol_percentage = round(sum(transactions$k_symbol == '') * 100 / dim(transactions)[1], 2)
missing_account_percentage = round(sum(is.na(transactions$account)) * 100 / dim(transactions)[1], 2)

print(paste("In der Kolonne Bank fehlen", missing_bank_percentage, "% der Werte."))
[1] "In der Kolonne Bank fehlen 74.11 % der Werte."
print(paste("In der Kolonne k_symbol fehlen", missing_k_symbol_percentage, "% der Werte."))
[1] "In der Kolonne k_symbol fehlen 45.62 % der Werte."
print(paste("In der Kolonne Account fehlen", missing_account_percentage, "% der Werte."))
[1] "In der Kolonne Account fehlen 72.04 % der Werte."

Bei den Kolonnen bank und account fehlen fast drei Viertel der Werte, bei k_symbol beinahe die Hälfte. Daher haben wir uns entschieden, diese Kolonnen vom Dataframe zu entfernen.

transactions$bank <- NULL
transactions$k_symbol <- NULL
transactions$account <- NULL

Jetzt schauen wir uns nochmals die fehlenden Werte an.

sum(is.na(transactions))
[1] 0

Durch das Entfernen der drei Spalten konnten alle fehlenden Werte entfernt werden. Nun besteht aber noch das Problem, dass der angegebene Betrag der Transaktionen immer positiv ist, auch wenn das Vermögen sinkt. Wir gehen also davon aus, dass immer der absolute Wert in diesere Kolonne erfasst ist. Dies müssen wir noch bereinigen, so dass der Betrag auch negativ sein kann.

Glücklicherweise gibt es eine weitere Information, welche uns bei diesem Problem weiterhilft: Die Variable “type”.

unique(transactions$type)
[1] "income"     "withdrawal"

Es gibt nur zwei Transaktionstypen: income und withdrawl. Diese geben an, ob Geld auf das Konto fliesst oder entnommen wird. Bei Transaktionen mit Typ “withdrawal” kann also der Betrag negiert werden. Wir fügen die neuen Variabeln “difference” und “prev_balance” hinzu. Sie beschreiben den Betrag mit entsprechendem Vorzeichen und den Kontostand vor der Transaktion.

df <- transactions

# Konvertieren Sie das 'date'-Feld in ein Datum
#df$date <- as.Date(df$date)

# Sortieren Sie das Dataframe nach Nutzer und Datum. Bei gleichem Datum wird nach tranaction_id sortiert. 
df <- df[order(df$account_id, df$date, df$trans_id), ]

# Gruppieren Sie das Dataframe nach Nutzer
df <- group_by(df, account_id)

# Iterieren Sie über jeden Nutzer und bearbeiten Sie die Transaktionen
df <- df %>% 
  summarize(transactions = {
    
    # Die Different der Transaktion zur Vorherigen wird mittels Typ ausgelesen
    difference <- ifelse(type == "withdrawal", amount, amount * -1)
    
    # Balance ist der Kontostand nach der Transaktion, mittels difference wird der Kontostand vor Transaktion ermittelt.
    prev_balance <- balance - difference

    # Erstellen Sie das Dataframe mit den Transaktionen für jeden Nutzer
    transactions_df <- data.frame(amount, date, balance, prev_balance, difference)
    transactions_df
  }) %>%
  ungroup()

transactions <- unnest(df, transactions)

sample_n(transactions, 5)

Orders

Die Tabelle Orders enthält Informationen über einen Zahlungsauftrag. Sie enthällt die Kolonnen Order-ID, Account-ID (welche auf die Tabelle Accounts verweist), Bank-To (welche beschreibt, an welche Bank die Zahlung geht), Account-To (welche auf die Tabelle Accounts verweist und den Empfänger der Zahlung angibt), Amount (den Betrag der Zahlung) und k_symbol (den Typ der Transaktion). Die Werte in der Spalte k_symbol sollen wieder übersetzt werden. Fehlende Werte sollen hier mit “unknown” ersetzt werden.

sample_n(orders, 5)
# Rename column k_symbol
orders <- rename(orders, "characterization" = "k_symbol") 

# Translate column characterization
orders$characterization[orders$characterization == "SIPO"]     <- "household"
orders$characterization[orders$characterization == "UVER"]     <- "loan"
orders$characterization[orders$characterization == "POJISTNE"] <- "insurance"
orders$characterization[orders$characterization == "LEASING"]  <- "leasing"
orders$characterization[orders$characterization == " "]  <- "unknown"
orders$characterization[orders$characterization == ""]  <- "unknown"

orders$amount <- as.numeric(orders$amount)

sum(is.na(orders))
[1] 0

Auch hier gibt es keine fehlenden Werte.

Loans

sample_n(loans, 5)

Die Tabelle Loans enthält Informationen über Darlehen der verschiedenen Accounts. Dabei stehen uns Informationen wie das Datum, die Höhe, die Dauer, der Betrag der zahlungen und den Status der Darlehen zur Verfügung. Auch hier soll wieder das Datumsformat geändert und der Status der Darlehen in einen besser lesbaren Wert umgewandelt werden. Die Informationen über die verschiedenen Status sind der Dokumentation des Datensatzes zu entnehmen.

loans$date <- as.Date(as.character(loans$date), format= "%y%m%d")
loans$payments <- as.numeric(loans$payments)
loans$amount <- as.numeric(loans$amount)

# Make column status human readable
loans$status[loans$status == "A"] <- "finished_payed"
loans$status[loans$status == "B"] <- "finished_not_payed"
loans$status[loans$status == "C"] <- "running_ok"
loans$status[loans$status == "D"] <- "running_in_debt"

sum(is.na(loans))
[1] 0

Auch hier gibt es keine fehlenden Werte.

Zusammenfügen der Dataframes

Als nächstes fassen wir die Dataframes zu einem grossen Dataframe zusammen, damit wir später Modelle darauf trainieren können. Dabei müssen wir überprüfen, ob es sich bei den Verbindungen der verschiedenen Tabellen um n zu n Beziehungen handelt. Wir starten mit der Tabelle Clients und fügen immer mehr Tabellen hinzu.

Clients und Dispositions

print(paste("Anzahl Kunden in Clients:", dim(clients)[1]))
[1] "Anzahl Kunden in Clients: 4650"
print(paste("Anzahl Dispositions:", dim(dispositions)[1]))
[1] "Anzahl Dispositions: 4500"

Es gibt also weniger Dispositions als Clients. Aber gibt es Clients mit mehreren Dispositions?

length(unique(dispositions$client_id))
[1] 4500

Nein, es gibt keine Clients mit mehreren Dispositionen. Wir können also die beiden Dataframes anhand eines Inner-Joins miteinander verbinden, verlieren dabei aber mehr als 600 Kunden. Diese Kunden sind für uns sowieso irrelevant, da sie keine Konten besitzen.

full <- inner_join(clients, dispositions, by = "client_id", suffix = c(".client", ".dispositions"))
print(paste("Anzahl Kunden mit Dispositionen:", dim(full)[1]))
[1] "Anzahl Kunden mit Dispositionen: 3858"

Es bleiben also 3858 Kunden.

Accounts

print(paste("Anzahl Accounts:", dim(accounts)[1]))
[1] "Anzahl Accounts: 4500"

Es gibt also gleich viele Accounts wie Dispositionen. Auch hier soll wieder überprüft werden, ob jeder Account genau einen Owner hat.

# Count the number of dispositions per client
acc_counts <- count(accounts, "client_id")

# Create a summary of the counts
summary <- table(acc_counts$n)

summary

4500 
   1 

Jeder Account hat genau einen Owner. Das Dataframe Accounts kann also zu unserem grossen Dataframe hinzugefügt werden.

full <- inner_join(full, accounts, by = "account_id", suffix = c("", ".accounts"))
dim(full)[1]
[1] 3858

Unser Dataframe hat immer noch eine Länge von 4500.

Loans

print(paste("Anzahl Loans:", dim(loans)[1]))
[1] "Anzahl Loans: 682"

Es gibt nur 682 Loans. Das bedeutet, dass nicht jeder Kunde ein Darlehen hat, was Sinn ergibt. Weiter soll noch überprüft werden, ob es Kunden gibt, welche mehrere Darlehen haben.

length(unique(loans$account_id))
[1] 682

Es gibt also keine Kunden, welche mehrere Darlehen bezogen haben. Die Loans sollen an das gesamte Dataframe mittels Left Join agehängt werden. So gibt es Kunden, welche keine Loans haben und die Variabeln den Wert NA haben. Bei diesen sollen die fehlenden numerischen Werte durch 0 ersetzt und der Status auf “no_loan” geändert werden. Das Datum des Loans entfernen wir.

full <- left_join(full, loans, by = "account_id", suffix = c("", ".loans"))
full$amount <- ifelse(is.na(full$amount), 0, full$amount)
full$duration <- ifelse(is.na(full$duration), 0, full$duration)
full$payments <- ifelse(is.na(full$payments), 0, full$payments)
full$status <- ifelse(is.na(full$status), "no_loan", full$status)
full$date.loans <- NULL

Cards

print(paste("Anzahl Cards:", dim(cards)[1]))
[1] "Anzahl Cards: 747"
length(unique(cards$disp_id))
[1] 747

Es gibt 747 Kunden mit Karten, kein Kunde hat mehrere Karten. Es macht wieder Sinn, dass nicht alle Kunden eine Karte habe. Der Typ der Karte ist immer gleich. Daher wird der Typ der Karte durch einen boolischen Wert “has_card” ersetzt. Dies wird auch unsere Zielvariabel in den Modellen sein. Auch die Card-ID kann entfernt werden.

full <- left_join(full, cards, by = "disp_id", suffix = c("", ".cards"))
has_card_function <- function(x) {
  if (is.na(x)) {
    return(FALSE)
  } else {
    return(TRUE)
  }
}

full$has_card <- sapply(full[, "card_id"], has_card_function)
full <- full %>% select(-card_id, -type)

Orders

print(paste("Anzahl Orders:", dim(orders)[1]))
[1] "Anzahl Orders: 6471"
length(unique(orders$account_id))
[1] 3758

Es gibt 6471 Orders für 3758 Kunden. Das bedeutet, dass es Kunden gibt, welche mehrere Orders haben. Diese Informationen müssen wir zu einer Row zusammenfassen.

Dabei haben wir uns dafür entschieden, die Variabeln account_to und amount nicht in den Gesamtdatensatz einfliessen zu lassen. Der amount ist bereits in den Transaktionen inbegriffen. An wen die Zahlungen gehen sollten keinen Einfluss auf unser Modell haben.

Also bleiben uns noch die Variabeln characterization und bank_to. Zuerst schauen wir uns an, wie viele verschiedene Werte diese beiden Variabeln annehmen können.

unique(orders$characterization)
[1] "household" "loan"      "unknown"   "insurance" "leasing"  
length(unique(orders$characterization))
[1] 5
unique(orders$bank_to)
 [1] "YZ" "ST" "QR" "WX" "CD" "AB" "UV" "GH" "IJ" "KL" "EF" "MN" "OP"
length(unique(orders$bank_to))
[1] 13

Bei diesen beiden Variabeln gibt es nur 5 bzw. 13 verschiedene Ausprägungen. Wir können also diese Werte wie folgt an den grossen Datensatz anfügen:

Für jede Ausprägung der Spalte gibt es eine neue Kolonne. Diese Kolonne nimmt den Wert der Anzahl der Orders mit dieser characterization bzw. bank_to an.

characterizations <- orders %>%
  group_by(account_id, characterization) %>%
  count()

characterizations_wide <- characterizations %>%
  spread(key = characterization, value = n)

bank_tos <- orders %>%
  group_by(account_id, bank_to) %>%
  count()

bank_tos_wide <- bank_tos %>%
  spread(key = bank_to, value = n)

orders <- merge(characterizations_wide, bank_tos_wide, by = "account_id")

orders[is.na(orders)] <- 0
full <- left_join(full, orders, by = "account_id")

Die fehlenden Werte in diesen Kolonnen müssen jetzt noch mit 0 ersetzt werden. Der Einfachheit halber setzen wir diese Werte hardcoded.

full$household[is.na(full$household)] <- 0
full$insurance[is.na(full$insurance)] <- 0
full$leasing[is.na(full$leasing)] <- 0
full$loan[is.na(full$loan)] <- 0
full$unknown[is.na(full$unknown)] <- 0
full$AB[is.na(full$AB)] <- 0
full$CD[is.na(full$CD)] <- 0
full$EF[is.na(full$EF)] <- 0
full$GH[is.na(full$GH)] <- 0
full$IJ[is.na(full$IJ)] <- 0
full$KL[is.na(full$KL)] <- 0
full$MN[is.na(full$MN)] <- 0
full$OP[is.na(full$OP)] <- 0
full$QR[is.na(full$QR)] <- 0
full$ST[is.na(full$ST)] <- 0
full$UV[is.na(full$UV)] <- 0
full$WX[is.na(full$WX)] <- 0
full$YZ[is.na(full$YZ)] <- 0

Districts

Jeder Kunde sowie jeder Account ist einem Gebiet zugewiesen. Zuerst überprüfen wir, ob es sich dabei immer um das selbe Gebiet handelt.

sum(full$district_id != full$district_id.accounts)
[1] 349

409 Personen haben ihren Account also in einem anderen Distrikt, als sie zugewiesen sind. Wir müssen also die District-Informationen für beide District-ID’s zuweisen. Dies machen wir wieder mit Left-Joins.

full <- left_join(full, districts, by = "district_id")

full <- left_join(full, districts, by = c("district_id.accounts"="district_id"), suffix = c("", ".accounts"))

Zum Schluss können die verschiedenen ID’s entfernt werden, da sie irrelevant für das Modell sind und nicht mehr gebraucht werden. Einzig die Account-ID wird im Datensatz behalten, damit die Transaktionen hinzugefügt werden können.

full <- full %>% select(-client_id, -district_id, -disp_id, -district_id.accounts, -loan_id)

Unser zusammengesetztes Dataframe sieht nun wie folgt aus:

full

Er beinhaltet 3858 Kunden mit 59 verschiedenen Merkmalen.

Transactions

Nun müssen nur noch die Transaktionsdaten zum Dataframe hinzugefügt werden. Diese müssen aber anders zusammengefasst werden, da jeder Kunde viele Transaktionen haben kann. Die Transaktionen werden für Kreditkartenkäufer und Nicht-Käufer aggregiert, weshalb wir diese als ersten anhand der Variable “has_card” trennen.

card_buyers <- full %>% filter(has_card == TRUE)
non_buyers <- full %>% filter(has_card == FALSE)

print(paste("Anzahl Käufer:", dim(card_buyers)[1]))
[1] "Anzahl Käufer: 706"
print(paste("Anzahl Nicht-Käufer:", dim(non_buyers)[1]))
[1] "Anzahl Nicht-Käufer: 3152"

Es gibt 706 Kreditkartenkäufer und 3152 Nicht-Käufer.

Aggregation der Transaktionen für Kreditkartenkäufer

Bei den Kreditkartenkäufern ist der Zeitpunkt des Kreditkartenkaufs gegeben. Da unser Modell Vorhersagen soll, ob ein Kunde eine Kredikarte kauft, haben die Monate unmittelbar vor dem Kaufzeitpunkt den grössten Einfluss. Wir erstellen also für jeden Kreditkartenkäufer ein Rollup-Fenster für die letzten zwölf Monate vor dem Kauf der Kreditkare, minus einem Monat Lag. Diesen Monat beachten wir nicht, da die Bearbeitung eines Kreditkartenkaufs etwa diese Zeitspanne dauert und sich der Kunde dementsprechend bereits vor dem Kaufdatum für eine Kreditkarte interessiert hat.

Als Erstes werden dafür die Transaktionen von Kunden herausgefiltert, welche eine Kreditkarte haben.

account_ids <- card_buyers$account_id
buyer_transactions <- transactions[transactions$account_id %in% account_ids,]

Das Kreditkartenkaufdatum soll zu den Transaktionen hinzugefügt werden, damit diese für jeden Kunden einzeln gefiltert werden können.

buyer_transactions <- merge(buyer_transactions, full[, c("account_id", "issued")], by="account_id")

Nun sollen Transaktionen so gefiltert werden, dass nur noch Transaktionen zwischen 13 Monaten und 1 Monat vor dem Anfang des Monats des Kreditkartenkaufes vorkommen.

filter_time_range <- function(df)
{
  filtered_df <- df %>%
    filter(date >= floor_date(issued %m-% months(13), "month") &
         date <= (floor_date(issued %m-% months(1), "month") - days(1)))
  return(filtered_df)
}

filtered_df <- filter_time_range(buyer_transactions)

Es existieren oft Monate, bei denen ein Kunde keine Transaktionen gemacht hat. Dies liegt entweder daran, dass der Kunde nicht viele Transaktionen tätigt oder die Kreditkarte bereits im ersten Jahr gekauft hat. In diesen Monaten werden in Folge jeweils eine leere Transaktion eingefügt, welche die Balance der letzten Transaktion des Vormonats trägt.

impute_missing_months <- function(filtered_df)
{
  filtered_df$month <- format(filtered_df$date, "%Y-%m")
  rows_to_impute <- filtered_df %>% group_by(account_id) %>% filter(n_distinct(month) != 12)
  rows_to_impute <- rows_to_impute[order(rows_to_impute$account_id, rows_to_impute$date), ]
  account_ids <- unique(rows_to_impute$account_id)
  print(paste(length(account_ids), "unvollständige Accounts gefunden."))
  pb <- txtProgressBar(min = 0, max = length(account_ids), style = 3, width = 50, char = "=")
  
  for (i in 1:length(account_ids))
  {
    user_transactions <- rows_to_impute[rows_to_impute$account_id == account_ids[i],]
    
    # get all months the client should have a transaction in
    months_target <- sapply(13:2, function(x) format(user_transactions$issued[1] %m-% months(x), "%Y-%m"))
    
    # create dummy transaction in case first month is empty
    new_transaction <- user_transactions[1,]
    new_transaction$difference = 0
    
    # use prev_balance in case the first element gets imputed
    new_transaction$balance <- new_transaction$prev_balance
    
    for (month_target in months_target)
    {
      new_transaction$date <- as.Date(paste0(month_target, "-01"))
      new_transaction$month <- month_target
      # get last transaction of current month from the current user's transactions
      last_transaction <- tail(user_transactions %>% filter(month == month_target), n=1)
      
      if(nrow(last_transaction) == 0)
      {
        # impute empty transaction in missing month
        filtered_df <- rbind(filtered_df, new_transaction)
      }
      else
      {
        new_transaction$balance <- last_transaction$balance
        new_transaction$prev_balance <- new_transaction$balance
      }
    }
    setTxtProgressBar(pb, i)
  }
  close(pb)
  return(filtered_df)
}

filtered_df <- impute_missing_months(filtered_df)
[1] "169 unvollständige Accounts gefunden."

  |                                                        
  |                                                  |   0%
  |                                                        
  |                                                  |   1%
  |                                                        
  |=                                                 |   1%
  |                                                        
  |=                                                 |   2%
  |                                                        
  |=                                                 |   3%
  |                                                        
  |==                                                |   4%
  |                                                        
  |==                                                |   5%
  |                                                        
  |===                                               |   5%
  |                                                        
  |===                                               |   6%
  |                                                        
  |===                                               |   7%
  |                                                        
  |====                                              |   7%
  |                                                        
  |====                                              |   8%
  |                                                        
  |====                                              |   9%
  |                                                        
  |=====                                             |   9%
  |                                                        
  |=====                                             |  10%
  |                                                        
  |=====                                             |  11%
  |                                                        
  |======                                            |  11%
  |                                                        
  |======                                            |  12%
  |                                                        
  |=======                                           |  13%
  |                                                        
  |=======                                           |  14%
  |                                                        
  |=======                                           |  15%
  |                                                        
  |========                                          |  15%
  |                                                        
  |========                                          |  16%
  |                                                        
  |========                                          |  17%
  |                                                        
  |=========                                         |  17%
  |                                                        
  |=========                                         |  18%
  |                                                        
  |=========                                         |  19%
  |                                                        
  |==========                                        |  20%
  |                                                        
  |==========                                        |  21%
  |                                                        
  |===========                                       |  21%
  |                                                        
  |===========                                       |  22%
  |                                                        
  |============                                      |  23%
  |                                                        
  |============                                      |  24%
  |                                                        
  |============                                      |  25%
  |                                                        
  |=============                                     |  25%
  |                                                        
  |=============                                     |  26%
  |                                                        
  |=============                                     |  27%
  |                                                        
  |==============                                    |  27%
  |                                                        
  |==============                                    |  28%
  |                                                        
  |==============                                    |  29%
  |                                                        
  |===============                                   |  30%
  |                                                        
  |===============                                   |  31%
  |                                                        
  |================                                  |  31%
  |                                                        
  |================                                  |  32%
  |                                                        
  |================                                  |  33%
  |                                                        
  |=================                                 |  33%
  |                                                        
  |=================                                 |  34%
  |                                                        
  |=================                                 |  35%
  |                                                        
  |==================                                |  36%
  |                                                        
  |==================                                |  37%
  |                                                        
  |===================                               |  37%
  |                                                        
  |===================                               |  38%
  |                                                        
  |====================                              |  39%
  |                                                        
  |====================                              |  40%
  |                                                        
  |====================                              |  41%
  |                                                        
  |=====================                             |  41%
  |                                                        
  |=====================                             |  42%
  |                                                        
  |=====================                             |  43%
  |                                                        
  |======================                            |  43%
  |                                                        
  |======================                            |  44%
  |                                                        
  |======================                            |  45%
  |                                                        
  |=======================                           |  46%
  |                                                        
  |=======================                           |  47%
  |                                                        
  |========================                          |  47%
  |                                                        
  |========================                          |  48%
  |                                                        
  |========================                          |  49%
  |                                                        
  |=========================                         |  49%
  |                                                        
  |=========================                         |  50%
  |                                                        
  |=========================                         |  51%
  |                                                        
  |==========================                        |  51%
  |                                                        
  |==========================                        |  52%
  |                                                        
  |==========================                        |  53%
  |                                                        
  |===========================                       |  53%
  |                                                        
  |===========================                       |  54%
  |                                                        
  |============================                      |  55%
  |                                                        
  |============================                      |  56%
  |                                                        
  |============================                      |  57%
  |                                                        
  |=============================                     |  57%
  |                                                        
  |=============================                     |  58%
  |                                                        
  |=============================                     |  59%
  |                                                        
  |==============================                    |  59%
  |                                                        
  |==============================                    |  60%
  |                                                        
  |==============================                    |  61%
  |                                                        
  |===============================                   |  62%
  |                                                        
  |===============================                   |  63%
  |                                                        
  |================================                  |  63%
  |                                                        
  |================================                  |  64%
  |                                                        
  |=================================                 |  65%
  |                                                        
  |=================================                 |  66%
  |                                                        
  |=================================                 |  67%
  |                                                        
  |==================================                |  67%
  |                                                        
  |==================================                |  68%
  |                                                        
  |==================================                |  69%
  |                                                        
  |===================================               |  69%
  |                                                        
  |===================================               |  70%
  |                                                        
  |====================================              |  71%
  |                                                        
  |====================================              |  72%
  |                                                        
  |====================================              |  73%
  |                                                        
  |=====================================             |  73%
  |                                                        
  |=====================================             |  74%
  |                                                        
  |=====================================             |  75%
  |                                                        
  |======================================            |  75%
  |                                                        
  |======================================            |  76%
  |                                                        
  |======================================            |  77%
  |                                                        
  |=======================================           |  78%
  |                                                        
  |=======================================           |  79%
  |                                                        
  |========================================          |  79%
  |                                                        
  |========================================          |  80%
  |                                                        
  |=========================================         |  81%
  |                                                        
  |=========================================         |  82%
  |                                                        
  |=========================================         |  83%
  |                                                        
  |==========================================        |  83%
  |                                                        
  |==========================================        |  84%
  |                                                        
  |==========================================        |  85%
  |                                                        
  |===========================================       |  85%
  |                                                        
  |===========================================       |  86%
  |                                                        
  |===========================================       |  87%
  |                                                        
  |============================================      |  88%
  |                                                        
  |============================================      |  89%
  |                                                        
  |=============================================     |  89%
  |                                                        
  |=============================================     |  90%
  |                                                        
  |=============================================     |  91%
  |                                                        
  |==============================================    |  91%
  |                                                        
  |==============================================    |  92%
  |                                                        
  |==============================================    |  93%
  |                                                        
  |===============================================   |  93%
  |                                                        
  |===============================================   |  94%
  |                                                        
  |===============================================   |  95%
  |                                                        
  |================================================  |  95%
  |                                                        
  |================================================  |  96%
  |                                                        
  |================================================= |  97%
  |                                                        
  |================================================= |  98%
  |                                                        
  |================================================= |  99%
  |                                                        
  |==================================================|  99%
  |                                                        
  |==================================================| 100%

Bei 169 Kreditkartenkäufer ist kein komplettes 12-Monats-Rollup-Fenster vorhanden. Bei allen wurden die fehlenden Monate imputiert.

Nun werden diese Daten aggregiert. Es wird eine Gruppierung anhand der account_id und des Monats gemacht. Die Werte “difference” und “balance” auf verschiedene Arten aggregiert: Auf beiden Werten erfassen wir das Minimum, das Maximum, den Durchschnitt, den Median und die Standardabweichung. Bei “balance” erfassen wir den ersten und den letzten Wert des Monats und bei “difference” die Anzahl positive und negative Differenzen.

get_summary_df <- function(filtered_df)
{
  summary_df <- filtered_df %>%
  group_by(account_id, month) %>%
  summarise(
    max_difference = max(difference),
    min_difference = min(difference),
    max_balance = max(balance),
    min_balance = min(balance),
    initial_balance = first(balance),
    end_balance = last(balance),
    mean_balance = mean(balance),
    median_balance = median(balance),
    std_balance = sd(balance),
    mean_difference = mean(difference),
    median_difference = median(difference),
    std_difference = sd(difference),
    count_positive_difference = sum(difference > 0),
    count_negative_difference = sum(difference < 0)
  )
  summary_df <- summary_df %>% arrange(account_id)
  return(summary_df)
}

summary_df <- get_summary_df(filtered_df)

Diese Zusammenfassung sieht nun für jeden Kunden folgendermassen aus:

summary_df %>% filter(account_id == 7)

Für jeden Kunden gibt es nun für jeden Monat vor dem Kreditkartenkauf eine Row mit den aggregierten Daten des Monats. Wir überprüfen noch kurz, ob auch wirklich für jeden Kunden zwölf Monate vorhanden sind.

get_incomplete_buyers <- function(summary_df)
{
  # Kontrolle, ob für jeden account_id 12 monate vorhanden sind
  month_counts <- summary_df %>%
      group_by(account_id) %>%
  summarise(month_count = n_distinct(month))

  # Prüfe, ob jedes account_id 12 Monate hat
  month_counts <- month_counts %>% filter(month_count != 12)
  return(month_counts)
}

month_counts <- get_incomplete_buyers(summary_df)
print(paste("Anzahl Kunden mit weniger als 12 Monaten:", dim(month_counts)[1]))
[1] "Anzahl Kunden mit weniger als 12 Monaten: 0"

Jeder Kreditkartenkäufer hat also wirklich 12 Monate. Als nächstes wollen wir diese zwölf Rows pro Kunde zu einer Row umwandeln. Dafür nummerieren wir zunächst die Monate pro Kreditkartenkäufer von 1 bis 12 durch. Dabei ist der Monat 12 der letzte Monat vor dem Kaufdatum (abgesehen vom Lag-Monat).

set_month_ids <- function(summary_df)
{
  # Sortieren nach account_id und Monat
  summary_df <- summary_df[order(summary_df$account_id, rev(summary_df$month)),]
  
  # Hinzufügen der Monatsnummer
  summary_df$group_id <- ave(seq_along(summary_df$account_id), summary_df$account_id, FUN = function(x) {x})
  summary_df$month_number <- 12
  
  for (i in 2:nrow(summary_df)) {
    if (summary_df$account_id[i] != summary_df$account_id[i-1]) {
      summary_df$month_number[i] <- 12
    } else {
      summary_df$month_number[i] <- summary_df$month_number[i-1] - 1
    }
  }
  
  # Entferne die Spalte group_id
  summary_df$group_id <- NULL
  summary_df$month <- NULL
  return(summary_df)
}

summary_df <- set_month_ids(summary_df)

Als nächstes brauchen wir pivot_wider. So haben wir jede Kennzahl zwölf mal als Kolonne, jedes Mal mit der vorher erstellten Monatsnummer als Suffix.

pivot_by_month <- function(summary_df)
{
  summary_df_wide <- summary_df %>%
    group_by(account_id) %>%
    pivot_wider(names_from = month_number,
              values_from = c(max_difference, min_difference, max_balance, min_balance, initial_balance, end_balance, mean_balance, median_balance, std_balance, median_balance, std_balance, mean_difference, median_difference, std_difference, count_positive_difference, count_negative_difference))
  return(summary_df_wide)
}

summary_df_wide <- pivot_by_month(summary_df)

Das daraus resultierende Dataframe sieht so aus:

summary_df_wide

Für jeden Kreditkartenkäufer gibt es jetzt eine Row mit 169 verschiedenen Merkmalen: Die account_id und die 14 aggregierten Informationen über die Transaktiionen für alle 12 Monate.

Dieses Dataframe fügen wir noch zu den anderen Informationen der Kreditkartenkäufer hinzu.

summary_df_buyers <- merge(summary_df_wide, subset(card_buyers), by = "account_id")

Aggregation der Transaktionen für Nicht-Käufer

Für Nicht-Käufer ist kein Datum des Kreditkartenkaufs gegeben, da sie ja keine gekauft haben. Es stellt sich also die Frage, von welchem Datum aus man das Rollup-Fenster erstellt.

Wir haben uns dazu entschieden, für jeden Käufer einen möglichst ähnlichen Nicht-Käufer zu finden. Dafür werden Nichtkäufer mit gleichem Geschlecht und gleicher Region gesucht. Das Kaufdatum des ähnlichen Käufers wird dann auf den Nicht-Käufer übertragen. Ein weiteres Kriterium ist, dass der Nicht-Käufer mindestens eine Transaktion in dem Rollup-Fenster haben muss. Jeder Nicht-Käufer wird maximal einmal als Match genommen, damit keine doppelten Kunden im Datensatz vorkommen.

Durch dieses Vorgehen benutzen wir nur einen Teil der Nicht-Käufer, haben aber direkte, ähnliche Vergleichspersonen, welche im gleichen Zeitraum keine Kreditkarten gekauft haben. Ausserdem bringt das Vorgehen auch den Vorteil, dass die Daten für das Modell automatisch Balanced sind. Dies macht das Trainieren des Modells einfacher, da es dazu beiträgt, dass das Modell nicht Biased gegenüber den Nicht-Käufern wird.

pb <- txtProgressBar(min = 0, max = nrow(card_buyers), style = 3, width = 50, char = "=")

  |                                                        
  |                                                  |   0%
# Erstelle ein leeres DataFrame "similar_non_buyers"
similar_non_buyers <- data.frame()

# Iteriere über jeden Kunden im DataFrame "buyers"
for (i in 1:nrow(card_buyers)) 
{
  # Wähle den aktuellen Kunden aus dem DataFrame "buyers"
  current_buyer <- card_buyers[i, ]
  
  similar_non_buyers_temp <- non_buyers %>% filter(gender == current_buyer$gender & 
                                                   region == current_buyer$region)
  
  # damit nicht der gleiche non_buyer doppelt verwendet wird
  similar_non_buyers_temp <- similar_non_buyers_temp[!similar_non_buyers_temp$account_id %in% similar_non_buyers$account_id,]
  
  # filtere non-buyers heraus, welche keine Transaktionen im relevanten Zeitraum vor issued haben
  similar_non_buyers_temp$issued <- current_buyer$issued
  non_buyer_transactions <- merge(transactions, similar_non_buyers_temp[, c("account_id", "issued")], by='account_id')
  non_buyer_filtered <- filter_time_range(non_buyer_transactions)
  non_buyer_ids <- unique(non_buyer_filtered$account_id)
  
  similar_non_buyers_temp <- similar_non_buyers_temp[similar_non_buyers_temp$account_id %in% non_buyer_ids,]
           
  if(nrow(similar_non_buyers_temp) > 0)
  {
    # Wähle den am besten passenden Kunden aus "similar_non_buyers_temp" aus
    best_match_index <- which.min(abs(similar_non_buyers_temp$age - current_buyer$age))
    best_match <- similar_non_buyers_temp[best_match_index, ]
    
    similar_non_buyers <- rbind(similar_non_buyers, copy(best_match))
    setTxtProgressBar(pb, i)
  }
  else
  {
    print(paste0(current_buyer$account_id, ": no non-buyer found"))
  }
}

  |                                                        
  |                                                  |   1%
  |                                                        
  |=                                                 |   1%
  |                                                        
  |=                                                 |   2%
  |                                                        
  |=                                                 |   3%
  |                                                        
  |==                                                |   3%
  |                                                        
  |==                                                |   4%
  |                                                        
  |==                                                |   5%
  |                                                        
  |===                                               |   5%
  |                                                        
  |===                                               |   6%
  |                                                        
  |===                                               |   7%
  |                                                        
  |====                                              |   7%
  |                                                        
  |====                                              |   8%
  |                                                        
  |====                                              |   9%
  |                                                        
  |=====                                             |   9%
  |                                                        
  |=====                                             |  10%
  |                                                        
  |=====                                             |  11%
  |                                                        
  |======                                            |  11%
  |                                                        
  |======                                            |  12%
  |                                                        
  |======                                            |  13%
  |                                                        
  |=======                                           |  13%
  |                                                        
  |=======                                           |  14%
  |                                                        
  |=======                                           |  15%
  |                                                        
  |========                                          |  15%
  |                                                        
  |========                                          |  16%
  |                                                        
  |========                                          |  17%
  |                                                        
  |=========                                         |  17%
  |                                                        
  |=========                                         |  18%
  |                                                        
  |=========                                         |  19%
  |                                                        
  |==========                                        |  19%
  |                                                        
  |==========                                        |  20%
  |                                                        
  |==========                                        |  21%
  |                                                        
  |===========                                       |  21%
  |                                                        
  |===========                                       |  22%
  |                                                        
  |===========                                       |  23%
  |                                                        
  |============                                      |  23%
  |                                                        
  |============                                      |  24%
  |                                                        
  |============                                      |  25%
  |                                                        
  |=============                                     |  25%
  |                                                        
  |=============                                     |  26%
  |                                                        
  |=============                                     |  27%
  |                                                        
  |==============                                    |  27%
  |                                                        
  |==============                                    |  28%
  |                                                        
  |==============                                    |  29%
  |                                                        
  |===============                                   |  29%
  |                                                        
  |===============                                   |  30%
  |                                                        
  |===============                                   |  31%
  |                                                        
  |================                                  |  31%
  |                                                        
  |================                                  |  32%
  |                                                        
  |================                                  |  33%
  |                                                        
  |=================                                 |  33%
  |                                                        
  |=================                                 |  34%
  |                                                        
  |=================                                 |  35%
  |                                                        
  |==================                                |  35%
  |                                                        
  |==================                                |  36%
  |                                                        
  |==================                                |  37%
  |                                                        
  |===================                               |  37%
  |                                                        
  |===================                               |  38%
  |                                                        
  |===================                               |  39%
  |                                                        
  |====================                              |  39%
  |                                                        
  |====================                              |  40%
  |                                                        
  |====================                              |  41%
  |                                                        
  |=====================                             |  41%
  |                                                        
  |=====================                             |  42%
  |                                                        
  |=====================                             |  43%
  |                                                        
  |======================                            |  43%
  |                                                        
  |======================                            |  44%
  |                                                        
  |======================                            |  45%
  |                                                        
  |=======================                           |  45%
  |                                                        
  |=======================                           |  46%
  |                                                        
  |=======================                           |  47%
  |                                                        
  |========================                          |  47%
  |                                                        
  |========================                          |  48%
  |                                                        
  |========================                          |  49%
  |                                                        
  |=========================                         |  49%
  |                                                        
  |=========================                         |  50%
  |                                                        
  |=========================                         |  51%
  |                                                        
  |==========================                        |  51%
  |                                                        
  |==========================                        |  52%
  |                                                        
  |==========================                        |  53%
  |                                                        
  |===========================                       |  53%
  |                                                        
  |===========================                       |  54%
  |                                                        
  |===========================                       |  55%
  |                                                        
  |============================                      |  55%
  |                                                        
  |============================                      |  56%
  |                                                        
  |============================                      |  57%
  |                                                        
  |=============================                     |  57%
  |                                                        
  |=============================                     |  58%
  |                                                        
  |=============================                     |  59%
  |                                                        
  |==============================                    |  59%
  |                                                        
  |==============================                    |  60%
  |                                                        
  |==============================                    |  61%
  |                                                        
  |===============================                   |  61%
  |                                                        
  |===============================                   |  62%
  |                                                        
  |===============================                   |  63%
  |                                                        
  |================================                  |  63%
  |                                                        
  |================================                  |  64%
  |                                                        
  |================================                  |  65%
  |                                                        
  |=================================                 |  65%
  |                                                        
  |=================================                 |  66%
  |                                                        
  |=================================                 |  67%
  |                                                        
  |==================================                |  67%
  |                                                        
  |==================================                |  68%
  |                                                        
  |==================================                |  69%
  |                                                        
  |===================================               |  69%
  |                                                        
  |===================================               |  70%
  |                                                        
  |===================================               |  71%
  |                                                        
  |====================================              |  71%
  |                                                        
  |====================================              |  72%
  |                                                        
  |====================================              |  73%
  |                                                        
  |=====================================             |  73%
  |                                                        
  |=====================================             |  74%
  |                                                        
  |=====================================             |  75%
  |                                                        
  |======================================            |  75%
  |                                                        
  |======================================            |  76%
  |                                                        
  |======================================            |  77%
  |                                                        
  |=======================================           |  77%
  |                                                        
  |=======================================           |  78%
  |                                                        
  |=======================================           |  79%
  |                                                        
  |========================================          |  79%
  |                                                        
  |========================================          |  80%
  |                                                        
  |========================================          |  81%
  |                                                        
  |=========================================         |  81%
  |                                                        
  |=========================================         |  82%
  |                                                        
  |=========================================         |  83%
  |                                                        
  |==========================================        |  83%
  |                                                        
  |==========================================        |  84%
  |                                                        
  |==========================================        |  85%
  |                                                        
  |===========================================       |  85%
  |                                                        
  |===========================================       |  86%
  |                                                        
  |===========================================       |  87%
  |                                                        
  |============================================      |  87%
  |                                                        
  |============================================      |  88%
  |                                                        
  |============================================      |  89%
  |                                                        
  |=============================================     |  89%
  |                                                        
  |=============================================     |  90%
  |                                                        
  |=============================================     |  91%
  |                                                        
  |==============================================    |  91%
  |                                                        
  |==============================================    |  92%
  |                                                        
  |==============================================    |  93%
  |                                                        
  |===============================================   |  93%
  |                                                        
  |===============================================   |  94%
  |                                                        
  |===============================================   |  95%
  |                                                        
  |================================================  |  95%
  |                                                        
  |================================================  |  96%
  |                                                        
  |================================================  |  97%
  |                                                        
  |================================================= |  97%
  |                                                        
  |================================================= |  98%
  |                                                        
  |================================================= |  99%
  |                                                        
  |==================================================|  99%
  |                                                        
  |==================================================| 100%
close(pb)
print(paste("Anzahl Buyers:", length(unique(card_buyers$account_id))))
[1] "Anzahl Buyers: 706"
print(paste("Anzahl gefundene Matches:", length(unique(similar_non_buyers$account_id))))
[1] "Anzahl gefundene Matches: 706"

Wie hier sichtbar wird, konnte zu allen Kartenkäufer ein passender Nicht-Käufer gefunden werden. Auf diesen sollen die Transaktionsdaten jetzt gleich wie bei den Kreditkartenkäufern aggregiert werden. Als erstes werden die Transaktionen wieder auf das gegebene Rollup-Fenster gefiltert.

# find missings
non_buyer_transactions <- merge(transactions, similar_non_buyers[, c("account_id", "issued")], by='account_id')
filtered_df <- filter_time_range(non_buyer_transactions)

Als nächstes werden wieder Aggregationen für jeden Monat erstellt und fehlende Monate analog zu den Kreditkartenkäufern imputiert.

filtered_df <- impute_missing_months(filtered_df)
[1] "168 unvollständige Accounts gefunden."

  |                                                        
  |                                                  |   0%
  |                                                        
  |                                                  |   1%
  |                                                        
  |=                                                 |   1%
  |                                                        
  |=                                                 |   2%
  |                                                        
  |=                                                 |   3%
  |                                                        
  |==                                                |   4%
  |                                                        
  |==                                                |   5%
  |                                                        
  |===                                               |   5%
  |                                                        
  |===                                               |   6%
  |                                                        
  |===                                               |   7%
  |                                                        
  |====                                              |   7%
  |                                                        
  |====                                              |   8%
  |                                                        
  |====                                              |   9%
  |                                                        
  |=====                                             |  10%
  |                                                        
  |=====                                             |  11%
  |                                                        
  |======                                            |  11%
  |                                                        
  |======                                            |  12%
  |                                                        
  |=======                                           |  13%
  |                                                        
  |=======                                           |  14%
  |                                                        
  |=======                                           |  15%
  |                                                        
  |========                                          |  15%
  |                                                        
  |========                                          |  16%
  |                                                        
  |========                                          |  17%
  |                                                        
  |=========                                         |  17%
  |                                                        
  |=========                                         |  18%
  |                                                        
  |==========                                        |  19%
  |                                                        
  |==========                                        |  20%
  |                                                        
  |==========                                        |  21%
  |                                                        
  |===========                                       |  21%
  |                                                        
  |===========                                       |  22%
  |                                                        
  |===========                                       |  23%
  |                                                        
  |============                                      |  23%
  |                                                        
  |============                                      |  24%
  |                                                        
  |============                                      |  25%
  |                                                        
  |=============                                     |  26%
  |                                                        
  |=============                                     |  27%
  |                                                        
  |==============                                    |  27%
  |                                                        
  |==============                                    |  28%
  |                                                        
  |==============                                    |  29%
  |                                                        
  |===============                                   |  29%
  |                                                        
  |===============                                   |  30%
  |                                                        
  |===============                                   |  31%
  |                                                        
  |================                                  |  32%
  |                                                        
  |================                                  |  33%
  |                                                        
  |=================                                 |  33%
  |                                                        
  |=================                                 |  34%
  |                                                        
  |=================                                 |  35%
  |                                                        
  |==================                                |  35%
  |                                                        
  |==================                                |  36%
  |                                                        
  |==================                                |  37%
  |                                                        
  |===================                               |  38%
  |                                                        
  |===================                               |  39%
  |                                                        
  |====================                              |  39%
  |                                                        
  |====================                              |  40%
  |                                                        
  |=====================                             |  41%
  |                                                        
  |=====================                             |  42%
  |                                                        
  |=====================                             |  43%
  |                                                        
  |======================                            |  43%
  |                                                        
  |======================                            |  44%
  |                                                        
  |======================                            |  45%
  |                                                        
  |=======================                           |  45%
  |                                                        
  |=======================                           |  46%
  |                                                        
  |========================                          |  47%
  |                                                        
  |========================                          |  48%
  |                                                        
  |========================                          |  49%
  |                                                        
  |=========================                         |  49%
  |                                                        
  |=========================                         |  50%
  |                                                        
  |=========================                         |  51%
  |                                                        
  |==========================                        |  51%
  |                                                        
  |==========================                        |  52%
  |                                                        
  |==========================                        |  53%
  |                                                        
  |===========================                       |  54%
  |                                                        
  |===========================                       |  55%
  |                                                        
  |============================                      |  55%
  |                                                        
  |============================                      |  56%
  |                                                        
  |============================                      |  57%
  |                                                        
  |=============================                     |  57%
  |                                                        
  |=============================                     |  58%
  |                                                        
  |=============================                     |  59%
  |                                                        
  |==============================                    |  60%
  |                                                        
  |==============================                    |  61%
  |                                                        
  |===============================                   |  61%
  |                                                        
  |===============================                   |  62%
  |                                                        
  |================================                  |  63%
  |                                                        
  |================================                  |  64%
  |                                                        
  |================================                  |  65%
  |                                                        
  |=================================                 |  65%
  |                                                        
  |=================================                 |  66%
  |                                                        
  |=================================                 |  67%
  |                                                        
  |==================================                |  67%
  |                                                        
  |==================================                |  68%
  |                                                        
  |===================================               |  69%
  |                                                        
  |===================================               |  70%
  |                                                        
  |===================================               |  71%
  |                                                        
  |====================================              |  71%
  |                                                        
  |====================================              |  72%
  |                                                        
  |====================================              |  73%
  |                                                        
  |=====================================             |  73%
  |                                                        
  |=====================================             |  74%
  |                                                        
  |======================================            |  75%
  |                                                        
  |======================================            |  76%
  |                                                        
  |======================================            |  77%
  |                                                        
  |=======================================           |  77%
  |                                                        
  |=======================================           |  78%
  |                                                        
  |=======================================           |  79%
  |                                                        
  |========================================          |  79%
  |                                                        
  |========================================          |  80%
  |                                                        
  |========================================          |  81%
  |                                                        
  |=========================================         |  82%
  |                                                        
  |=========================================         |  83%
  |                                                        
  |==========================================        |  83%
  |                                                        
  |==========================================        |  84%
  |                                                        
  |==========================================        |  85%
  |                                                        
  |===========================================       |  85%
  |                                                        
  |===========================================       |  86%
  |                                                        
  |===========================================       |  87%
  |                                                        
  |============================================      |  88%
  |                                                        
  |============================================      |  89%
  |                                                        
  |=============================================     |  89%
  |                                                        
  |=============================================     |  90%
  |                                                        
  |==============================================    |  91%
  |                                                        
  |==============================================    |  92%
  |                                                        
  |==============================================    |  93%
  |                                                        
  |===============================================   |  93%
  |                                                        
  |===============================================   |  94%
  |                                                        
  |===============================================   |  95%
  |                                                        
  |================================================  |  95%
  |                                                        
  |================================================  |  96%
  |                                                        
  |================================================= |  97%
  |                                                        
  |================================================= |  98%
  |                                                        
  |================================================= |  99%
  |                                                        
  |==================================================|  99%
  |                                                        
  |==================================================| 100%
summary_df_non_buyers <- get_summary_df(filtered_df)

Auch hier haben 168 Kunden kein vollständiges Rollup-Fenster. Wir überprüfen wieder, ob für jeden Kunden alle 12 Monate vorhanden sind.

month_counts <- get_incomplete_buyers(summary_df_non_buyers)
print(paste("Anzahl Kunden mit weniger als 12 Monaten:", dim(month_counts)[1]))
[1] "Anzahl Kunden mit weniger als 12 Monaten: 0"

Die Imputation hat funktioniert, es gibt keine Kunden mit weniger als 12 Monaten. Nun werden die Daten wieder auf eine Row ausgeweitet und mit den anderen Informationen der Kunden zusammengefügt.

summary_df_non_buyers <- set_month_ids(summary_df_non_buyers)
summary_df_wide <- pivot_by_month(summary_df_non_buyers)
summary_df_non_buyers <- merge(summary_df_wide, similar_non_buyers, by = "account_id")

Zum Schluss werden die Käufer und die Nicht-Käufer in einem Datensatz kombiniert. Die Account-ID kann noch entfernt werden.

final_df <- rbind(summary_df_buyers, summary_df_non_buyers)
final_df$account_id <- NULL

Ausserdem scheinen einige Variabeln als Faktoren im Datensatz zu sein, welche eigentlich numerisch wären.

final_df$unemployment_rate_95 <- as.numeric(final_df$unemployment_rate_95)
Warning: NAs introduced by coercion
final_df$unemployment_rate_95.accounts <- as.numeric(final_df$unemployment_rate_95.accounts)
Warning: NAs introduced by coercion
final_df$crimes_95 <- as.numeric(final_df$crimes_95)
Warning: NAs introduced by coercion
final_df$crimes_95.accounts <- as.numeric(final_df$crimes_95.accounts)
Warning: NAs introduced by coercion

Bei den berechneten Standardabweichung kommt es auch zu NA-Werten, da es Monate mit nur einer Transaktion gibt und daraus eine Null-Divison entsteht. Diese fehlenden Werte werden durch 0 ersetzt.

std_cols <- colnames(final_df %>% select(starts_with("std")))
final_df[,std_cols] <- final_df[,std_cols] %>% replace(is.na(.), 0)

Unser finales Dataframe sieht wie folgt aus:

final_df

Der Datensatz enthält 1412 Kunden mit 226 verschiedenen Variabeln. Die Hälfte der Kunden sind Kreditkartenkäufer. Die Zielvariabel für spätere Modelle ist “has_card”.

EDA

Nun wollen wir unser Dataframe noch ein wenig genauer unter die Luppe nehmen.

Anzahl Kreditkarten-Käufer und Nicht-Käufer

ggplot(data = final_df, aes(x = has_card)) +
  geom_bar(aes(y = after_stat(count)), stat = "count", fill = "steelblue") +
  labs(x = "Ist Käufer", y = "Anzahl", title = "Anzahl Kreditkarten-Käufer und Nicht-Käufer")

Da wir für jeden Kreditkartenkäufer genau einen Käufer suchen, ist der Datensatz ausgeglichen: Es hat gleiche viele Nicht-Käufer wie Käufer.

Verteilung der Kaufdaten

ggplot(final_df, aes(x = issued, fill = factor(has_card))) +
  geom_density(alpha = 0.5) +
  labs(x = "Kaufdatum", y = "Dichte", fill = "Has Card", title = "Verteilung der Kaufdaten") +
  scale_x_date(limits = range(final_df$issued))

Dieses Diagramm zeigt die Verteilung der Kaufdaten der Kunden. Da das Kaufdatum des Nicht-Käufers auf das des Käufers gesetzt wird, sind die Verteilungen der beiden Klassen hier deckungsgleich. In den Anfangsjahren des Datensatzes gibt es noch sehr wenige Kreditkartenkäufe, die Tendenz ist jedoch steigend. Ab 1996 erleben die Kreditkartenkäufe einen steilen Aufschwung, welcher Mitte 1998 zu seinem Höhepunkt kommt. Danach werden wieder weniger Käufe getätigt.

Entwicklung der Vermögen

Als nächstes wollen wir das Vermögen (balance) der Kunden anschauen. Wir beobachten, ob sich das Vermögen von Kunden und nicht Kunden bezüglich Höhe und Entwicklung unterscheidet.

averages_true <- colMeans(final_df[final_df$has_card == TRUE,
                                   c("mean_balance_1", "mean_balance_2", "mean_balance_3",
                                     "mean_balance_4", "mean_balance_5", "mean_balance_6",
                                     "mean_balance_7", "mean_balance_8", "mean_balance_9",
                                     "mean_balance_10", "mean_balance_11", "mean_balance_12")])

averages_false <- colMeans(final_df[final_df$has_card == FALSE,
                                    c("mean_balance_1", "mean_balance_2", "mean_balance_3",
                                      "mean_balance_4", "mean_balance_5", "mean_balance_6",
                                      "mean_balance_7", "mean_balance_8", "mean_balance_9",
                                      "mean_balance_10", "mean_balance_11", "mean_balance_12")])

ggplot() +
  geom_line(aes(x = 1:12, y = averages_true, color = "Käufer")) +
  geom_line(aes(x = 1:12, y = averages_false, color = "Nicht-Käufer")) +
  labs(x = "Monat", y = "Durchschnittliches Vermögen", title = "Vermögensentwicklung") +
  scale_color_manual(name = "Kunden", values = c("Käufer" = "steelblue", "Nicht-Käufer" = "red")) +
  scale_x_continuous(limits = c(1, 12)) +
  scale_y_continuous(limits = c(0, NA)) +
  theme(legend.position = "right")

Kreditkartenkäufer haben im Schnitt ein höheres Vermögen als Nicht-Käufer. Das Vermögen aller Kunden steigt tendenziell, wobei das der Käufer steiler als das der Nicht-Käufer steigt. Ab dem siebten Monat scheint aber auch diese Vermögenssteigerung abzuflachen.

Anzahl Transaktionen

Als nächstes wollen wir untersuchen, ob sich Kreditkartenkäufer und Nicht-Käufer durch die Anzahl getätigter Transaktionen unterscheiden. Dafür schauen wir die durchschnittliche Anzahl Transaktionen für beide Klassen an.

avg_sum_negative_true <- mean(rowSums(final_df[final_df$has_card == TRUE, grep("count_negative_difference", colnames(final_df))]))
avg_sum_negative_false <- mean(rowSums(final_df[final_df$has_card == FALSE, grep("count_negative_difference", colnames(final_df))]))

avg_sum_positive_true <- mean(rowSums(final_df[final_df$has_card == TRUE, grep("count_positive_difference", colnames(final_df))]))
avg_sum_positive_false <- mean(rowSums(final_df[final_df$has_card == FALSE, grep("count_positive_difference", colnames(final_df))]))

avg_sum_both_true = avg_sum_negative_true + avg_sum_positive_true
avg_sum_both_false = avg_sum_negative_false + avg_sum_positive_false

ggplot() +
  geom_bar(aes(x = c("Käufer", "Nicht-Käufer"), y = c(avg_sum_both_true, avg_sum_both_false)), stat = "identity", fill = c("steelblue", "red")) +
  labs(x = "", y = "Durchschnittliche Anzahl Transaktionen", title = "Durchschnittliche Anzahl Transaktionen im ganzen Rollup-Fenster")

Kreditkartenkäufer haben also im Schnitt mehr Transaktionen getätigt als Nicht-Käufer im Rollup-Fenster. Doch wie Verhält es sich, wenn wir Anzahl positive und Anzahl negative Transaktionen separat betrachten?

ggplot() +
  geom_bar(aes(x = c("Käufer Ausgaben", "Nicht-Käufer Ausgaben", "Käufer Einnahmen", "Nicht-Käufer Einnahmen"), y = c(avg_sum_negative_true, avg_sum_negative_false, avg_sum_positive_true, avg_sum_positive_false)), stat = "identity", fill = c("steelblue", "red", "steelblue", "red")) +
  labs(x = "", y = "Durchschnittliche Anzahl Transaktionen", title = "Durchschnittliche Anzahl Transaktionen im ganzen Rollup-Fenster")

Beide Klassen haben mehr positive Transaktionen als negative, wobei Käufer bei positiven sowie bei negativen durchschnittlich mehr haben. Bei den Einnahmen ist der Unterschied ein wenig höher als bei den Ausgaben.

Darlehen

Hier soll untersucht werden, ob Käufer mehr Darlehen bei der Bank aufnehmen als Nicht-Käufer.

final_df
card_buyers_with_loan <- final_df[final_df$has_card & final_df$status != "no_loan",]
non_buyers_with_loan <- final_df[!final_df$has_card & final_df$status != "no_loan",]

card_buyers_with_loan_count <- length(card_buyers_with_loan$has_card)
non_buyers_with_loan_count <- length(non_buyers_with_loan$has_card)

ggplot() +
  geom_bar(aes(x = c("Käufer", "Nicht-Käufer"), y = c(card_buyers_with_loan_count, non_buyers_with_loan_count)), stat = "identity", fill = c("steelblue", "red")) +
  labs(x = "", y = "Anzahl", title = "Anzahl Kunden mit Darlehen")

Fast doppelt so viele Käufer wie Nicht-Käufer haben ein Darlehen aufgenommen.

Modelle

Als nächstes wollen wir Modelle trainieren, welche Anhand der Merkmale der Kunden vorhersagen sollen, ob ein Kunde ein Käufer ist oder nicht. Dieses Modell soll dann gebraucht werden, um zu erkennen, welche bestehenden Kunden ohne Kreditkarte am ehesten eine kaufen würden.

Funktionen zur Evaluierung von Modellen

Damit die Evaluierung von Modellen nicht redundanten Code enthält, wird hier versucht, Funktionen zu erstellen, welche bei verschiedenen Modellen gebraucht werden können.

Train-Test-Split

Als Vorbereitung für die Modelle müssen wir unsere Daten zu Trainings- und Testdaten unterteilen. Dafür erstellen wir eine Funktion, welche auf verschiedenen Varianten des Datensatz gebraucht werden kann.

split_data <- function(df, test_size = 0.2) {
  set.seed(27)
  df <- df[order(df$issued, decreasing = FALSE), ]
  
  # Get number of rows in the dataframe
  n_rows <- nrow(df)

# Calculate the number of rows in the test set (20% of the total number of rows)
n_test_rows <- floor(n_rows * 0.2)

df$issued <- NULL

# Create the train and test sets
train <- head(df, n_rows - n_test_rows)
test <- tail(df, n_test_rows)
  

#split <- createDataPartition(df$has_card, p = 1 - test_size, list = FALSE)
 # train <- df[split, ]
  #test <- df[-split, ]

# create cv folds
  train_sets <- c()
  val_sets <- c()

  set.seed(27)
  cv_folds <- vfold_cv(train, v = 10)
  # Iteriere über alle Folds
  for (i in 1:nrow(cv_folds)) 
  {
    # Wähle den aktuellen Trainings- und Validierungsdatensatz aus
    train_set <- cv_folds$splits[[i]] %>% analysis()
    val_set <- cv_folds$splits[[i]] %>% assessment()
  
    # Speichere den aktuellen Trainings- und Validierungsdatensatz in den Listen
    train_sets[[i]] <- train_set
    val_sets[[i]] <- val_set
  }
  
  return(list(train = train, test = test, cv_folds = cv_folds, train_sets = train_sets, val_sets = val_sets))
}

Metriken

Damit wir die verschiedenen Modelle besser evaluieren können, müssen wir die gleichen Kennzahlen und Auswertungen pro Modell machen. Zu diesem Zweck definieren wir einige Funktionen, damit wir weniger redundanten Code haben und unsere Ergebnisse in einem einheitliche, vergleichbaren Format daherkommen.

Dafür erstellen wir eine Funktion, die die Genauigkeit (Accuracy), Cohen’s Kappa, Matthews Korrelation, Präzision (Precision), Erinnerung (Recall) und den F1-Score für eine Reihe von Vorhersagen und deren entsprechenden wahren Werte berechnet.

Was bedeuten diese Kennzahlen genau?

Accuracy (Genauigkeit): Die Accuracy ist der Prozentsatz der Vorhersagen, die mit den tatsächlichen Werten übereinstimmen. Sie wird berechnet als Anzahl der korrekten Vorhersagen geteilt durch die Gesamtzahl der Vorhersagen.

Cohen’s Kappa (Cohen’s Kappa): Cohen’s Kappa ist eine Messgröße für die Qualität von binären Klassifikationen. Es wird verwendet, um die Übereinstimmung zwischen zwei Klassifikatoren zu messen, indem es die Übereinstimmung über der erwarteten Übereinstimmung durch Zufall berechnet. Ein Kappa-Wert von 1 bedeutet perfekte Übereinstimmung, ein Wert von 0 bedeutet keine Übereinstimmung, die besser ist als Zufall, und ein Wert von -1 bedeutet komplett falsche Klassifikationen.

Matthews correlation coefficient (Matthews Korrelation): Der Matthews Korrelation Coefficient (MCC) ist eine Messgröße für die Qualität von binären Klassifikationen. Er reicht von -1 bis 1, wobei ein Wert von 1 perfekte Klassifikation bedeutet, ein Wert von 0 eine Klassifikation, die nicht besser als Zufall ist, und ein Wert von -1 eine komplett falsche Klassifikation bedeutet.

Precision (Präzision): Die Präzision ist der Prozentsatz der Vorhersagen, die tatsächlich korrekt waren, unter der Annahme, dass alle Vorhersagen korrekt sind. Sie wird berechnet als Anzahl der korrekten Vorhersagen für die positive Klasse geteilt durch die Gesamtzahl der Vorhersagen für die positive Klasse.

Recall (Erinnerung): Der Recall ist der Prozentsatz der tatsächlich positiven Werte, die korrekt vorhergesagt wurden. Er wird berechnet als Anzahl der korrekten Vorhersagen für die positive Klasse geteilt durch die Gesamtzahl der tatsächlich positiven Werte.

F1 score (F1-Wert): Der F1-Wert ist ein Maß für die Qualität von binären Klassifikationen, das die Harmoniesche Mischung von Präzision und Recall darstellt. Es wird berechnet als der Harmoniesche Mittelwert von Präzision und Recall. Ein hoher F1-Wert bedeutet, dass sowohl Präzision als auch Recall hoch sind.

get_metrics <- function(predictions, true_values, model_name) {
  # Calculate accuracy
  accuracy <- sum(predictions == true_values) / length(predictions)
  
  # Calculate Cohen's kappa
  n <- length(predictions)
  observed_agreement <- sum(predictions == true_values)
  expected_agreement <- sum(predictions == true_values) / n
  kappa <- (observed_agreement - expected_agreement) / (n - expected_agreement)
  
  # Calculate Matthews correlation coefficient
  confusion_matrix <- table(predictions, true_values)
  tp <- confusion_matrix[2,2]
  tn <- confusion_matrix[1,1]
  fp <- confusion_matrix[2,1]
  fn <- confusion_matrix[1,2]
  matthews <- (tp * tn - fp * fn) / sqrt((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn))
  
  # Calculate precision and recall
  precision <- confusion_matrix[2,2] / sum(confusion_matrix[2,])
  recall <- confusion_matrix[2,2] / sum(confusion_matrix[,2])
  
  # Calculate F1 score
  f1 <- 2 * (precision * recall) / (precision + recall)
  
  # Create a data frame of the metrics
  metrics <- data.frame(model = model_name, accuracy = accuracy, kappa = kappa, matthews = matthews,
                        precision = precision, recall = recall, f1 = f1)
  
  # Return the data frame
  return(metrics)
}

Konfusionsmatrix mit Plot

Diese Funktion erstellt eine Konfusionsmatrix und gibt sie als Plot zurück.

Eine Konfusionsmatrix ist ein wichtiges Werkzeug zur Evaluation von Klassifikationsmodellen. Sie zeigt an, wie gut das Modell in der Lage ist, die verschiedenen Klassen richtig zu identifizieren. In einer Konfusionsmatrix werden die tatsächlichen und die von dem Modell vorhergesagten Klassen gegenübergestellt. Die Matrix ist in vier Quadranten unterteilt: true positives (TP), true negatives (TN), false positives (FP) und false negatives (FN). TP sind die Fälle, in denen das Modell die Klasse richtig vorhergesagt hat, TN sind die Fälle, in denen das Modell die Klasse richtig vorhergesagt hat und diese Klasse auch tatsächlich vorliegt, FP sind die Fälle, in denen das Modell eine Klasse vorhergesagt hat, die in Wirklichkeit nicht vorliegt, und FN sind die Fälle, in denen das Modell eine Klasse nicht vorhergesagt hat, die in Wirklichkeit vorliegt. Eine Konfusionsmatrix ist hilfreich, um die Genauigkeit, Sensitivität und Spezifität des Modells zu berechnen und um zu sehen, an welchen Stellen das Modell Schwächen hat. Sie kann auch verwendet werden, um die Leistung von verschiedenen Modellen miteinander zu vergleichen.

plot_confusion_matrix <- function(predictions, true_values) {
  # Erstelle eine Confusion Matrix als Data Frame
  confusion_matrix_df <- data.frame(predictions, true_values)
  
  # Zähle die Häufigkeiten jeder Kombination von Vorhersage- und True-Werten
  counts_df <- count(confusion_matrix_df, predictions, true_values)
  
  # Erstelle einen ggplot-Plot
  ggplot(data = counts_df, aes(x = predictions, y = true_values)) +
    geom_tile(aes(fill = n)) +
    geom_text(aes(label = n)) +
    scale_fill_gradient(low = "white", high = "darkgreen") +
    labs(x = "Predicted Class", y = "True Class", title = "Confusion Matrix")
}

ROC / AUC

Diese Funktion zeichnet die ROC-Kurve und berechnet die Area und Curve.

Die ROC-Kurve (Receiver Operating Characteristic curve) ist ein wichtiges Werkzeug zur Bewertung von Klassifikatoren. Sie zeigt die Leistung des Klassifikators bei verschiedenen Schwellenwerten an, die zur Unterscheidung zwischen zwei Klassen verwendet werden. Die ROC-Kurve ist besonders nützlich, wenn die beiden Klassen im Verhältnis unausgeglichen sind, wie es oft der Fall ist, wenn es darum geht, seltene Ereignisse wie Krankheiten oder Betrug zu erkennen.

Die ROC-Kurve ist auf der x-Achse der falsch-positiv-Rate (FPR) und auf der y-Achse der wahr-positiv-Rate (TPR) aufgetragen. Der FPR gibt an, wie viele falsch positive Ergebnisse es gibt, während der TPR angibt, wie viele wahr positive Ergebnisse erzielt werden. Ein perfekter Klassifikator würde eine ROC-Kurve haben, die im oberen linken Bereich beginnt und nach rechts oben verläuft, wobei alle Fälle korrekt klassifiziert werden. Ein zufälliger Klassifikator würde eine diagonal verlaufende ROC-Kurve haben, da die FPR und TPR zufällig verteilt sind.

Die AuC (Area Under the Curve) ist eine Metrik, die aus der ROC-Kurve berechnet wird und die Leistung des Klassifikators zusammenfasst. Sie gibt an, wie gut der Klassifikator im Vergleich zu einem zufälligen Klassifikator ist. Eine AUC von 1 bedeutet, dass das Modell perfekt in der Lage ist, positive und negative Klassen zu unterscheiden, während eine AUC von 0.5 bedeutet, dass das Modell keine bessere Leistung als Zufall erzielt. Die AUC kann Werte zwischen 0 und 1 annehmen. Eine AUC von 0 bedeutet, dass das Modell völlig inkorrekt ist. Im Allgemeinen gilt, je größer die AUC, desto besser ist das Modell im Vergleich zu anderen Modellen.

make_roc_plot_and_get_auc <- function(predictions, true_values)
{
  roc_curve <- roc(as.numeric(predictions), as.numeric(true_values) - 1)

  plot(roc_curve, xlab = "False Positive Rate", ylab = "True Positive Rate", main ="ROC Curve")

  auc <- auc(roc_curve)

  return(auc)
}

Threshold Plot


find_best_threshold <- function(predictions, actual_values) {
  best_threshold <- 0
  best_f1 <- 0
  thresholds <- seq(0.01, 0.99, 0.01)
  f1_scores <- rep(0, length(thresholds))

  for (i in 1:length(thresholds)) {
    # Set the threshold for the predictions
    predictions_threshold <- ifelse(predictions > thresholds[i], TRUE, FALSE)
  
    if (all(predictions_threshold)) {
      next
    } else if (all(!predictions_threshold)) {
      next
    }
    # Calculate the f1
    f1 <- get_metrics(predictions_threshold, actual_values, "best threshold")$f1
  
    f1_scores[i] <- f1
  
    # Update the best threshold and best f1 if necessary
    if (f1 > best_f1) {
      best_threshold <- thresholds[i]
      best_f1 <- f1
    }
  }

  # Display the best threshold and best recall
  print(paste("Best threshold:", best_threshold))
  print(paste("Best F1:", best_f1))

  plot(thresholds, f1_scores, type = "l", xlab = "Threshold", ylab = "F1 Score", main = "F1 Score with different thresholds")

  return(best_threshold)
}

Vorgehen

Um ein geeignets Modell zu finden, werden wir verschiedene Modell-Alogirthmen auf den Trainingsdaten anzuwenden und versuchen, diese zu optimieren. Wir starten dabei mit einem vorgegebenen Baseline Modell. Jedes Modell schauen wir kurz individuell an. Am Schluss werden die verschiedenen Modelle nochmals gesamthaft miteinander verglichen. Dabei schauen wir verschiedene Metriken, Top-N-Listen, Feature-Importance und weitere Informationen wie die ROC-Kurve oder Konfusionsmatrizen. Diese Informationen erhalten wir über 10-fache-Kreuzvalidierung. Wir trainieren also jedes Modell 10 Mal mit verschiedenen 90% der Trainingsdaten und berechnen unsere Metriken und weiteren Informationen auf den restlichen 10%.

Doch welche Metrik in unserer Liste ist die relevanteste für uns? Beim Modell geht es darum, Kunden zu finden, welche am ehesten eine Kreditkarte kaufen würden. Diesen Kunden würden dann für die Bank interessant werden und eine gewisse Massnahme würde ergriffen werden, sei es das Schicken einer Werbung oder das Unterbreiten eines Angebots. Da dies mit einem zeitlichen Aufwand verbunden ist und Kunden, welche ein solches Angebot oder eine Werbung bekommen aber kein Interesse haben, eher genervt davon werden würden, sollen die vom Modell als positiv klassifizierten Fälle möglichst tatsächlich positiv sein. Daher schauen wir bei den Modellen vor allem auf die Precision.

Baseline Modell

Als erstes soll eine logistische Regression mit den Informationen Alter, Geschlecht, Domizilregion, Vermögen (balance-Schnitt über alle Monate) und Umsatz (difference-Schnitt über alle Monate) als Baseline Modell erstellt werden.Dafür müssen wir kurz ein neues Dataframe erstellen.

baseline_data <- final_df
baseline_data$mean_balance <- rowMeans(final_df[, c("mean_balance_1", "mean_balance_2", "mean_balance_3", "mean_balance_4", 
                                                "mean_balance_5", "mean_balance_6", "mean_balance_7", "mean_balance_8", 
                                                "mean_balance_9", "mean_balance_10", "mean_balance_11", "mean_balance_12")])
baseline_data$mean_difference <- rowMeans(final_df[, c("mean_difference_1", "mean_difference_2", "mean_difference_3", "mean_difference_4", 
                                                  "mean_difference_5", "mean_difference_6", "mean_difference_7", "mean_difference_8", 
                                                  "mean_difference_9", "mean_difference_10", "mean_difference_11", "mean_difference_12")])
baseline_data <- baseline_data[, c("age", "gender", "region", "has_card", "mean_balance", "mean_difference", "issued")]
baseline_data$has_card <- as.factor(baseline_data$has_card)

Darauf wird ein Train-Test-Split mit 80% Trainingsdaten und 20% Testdaten gebraucht.

splits <- split_data(baseline_data, test_size = 0.2)
train <- splits$train
test <- splits$test
train_sets <- splits$train_sets
val_sets <- splits$val_sets

Das Regressionsmodell wird auf den Trainingsdaten trainiert. Wir verwenden die Funktion glm von der Library “stats”. Bei jedem Modell müssen die Predictions gemacht und damit die Metriken erstellt werden. Da die logistische Regression eine Zahl zwischen 0 und 1 zurückgibt, müssen wir anhand eines Thresholds die Werte zu TRUE und FALSE umwandeln. Der Threshold gibt an, ab welchem Wert eine Vorhersage als positiv betrachtet wird. Standardmäßig ist der Threshold auf 0.5 gesetzt, was bedeutet, dass alle Kunden mit einem Wert grösser als 0.5 als Kartenkäufer betrachtet werden.Die Werte für jede Validierung werden in einer Liste gespeichert. Als erstes wollen wir uns die durchschnittlichen Metriken anschauen.

regression_baseline_metrics <- list()
regression_baseline_predictions <- list()
regression_baseline_predictions_threshold <- list()

for(i in 1:length(val_sets)) {
  
  regression_baseline <- glm(has_card ~ ., data = train_sets[[i]], family = binomial)
  
  regression_baseline_predictions[[i]] <- predict(regression_baseline, val_sets[[i]], type = "response")
  
  threshold <- 0.5
  
  regression_baseline_predictions_threshold[[i]] <- ifelse(regression_baseline_predictions[[i]] > threshold, TRUE, FALSE)

  regression_baseline_metrics[[i]] <- get_metrics(regression_baseline_predictions_threshold[[i]], val_sets[[i]]$has_card, "Logistic Regression Baseline")
  
}

regression_baseline_metrics <- do.call("rbind", regression_baseline_metrics)
metrics_all <- regression_baseline_metrics

colMeans(regression_baseline_metrics %>% select(-model))
 accuracy     kappa  matthews precision    recall        f1 
0.7778761 0.7763595 0.5589070 0.7795205 0.7846058 0.7801271 
#regression_baseline_explainer <- explain(regression_baseline, data = train)

Um die Konfusionsmatrix anzuzeigen berechnen wir den Schnitt des Wertes der logistischen Regression für jeden Kunden

#plot_confusion_matrix(regression_baseline_predictions_threshold, test$has_card)
#make_roc_plot_and_get_auc(regression_baseline_predictions, test$has_card)

Regressionsmodell mit allen Daten

Nun soll dieses Baseline-Modell verbessert werden. Als erstes probieren wir, das gleiche Modell (Logistische Regression) mit mehr Input-Parametern zu trainieren.

Da die logistische Regression Probleme mit Faktoren hat, welche nur im Trainings- bzw. Testdatensatz vorkommen und auch nicht gut mit NA’s umgehen kann, müssen wir zuerst noch einige Anpassungen am Datensatz vornehmen.

Als erstes entfernen wir alle Kolonnen, welche Faktoren sind und mehr als 10 verschiedene Ausprägungen haben.

# Ermittle die numerischen Merkmale in den Trainingsdaten
numeric_vars <- sapply(final_df, is.numeric)

# Erstelle ein Subset der Trainingsdaten ohne die numerischen Merkmale
train_no_numeric <- final_df[, !numeric_vars]

# Ermittle die Anzahl der Kategorien für jedes Merkmal
num_categories <- sapply(train_no_numeric, function(x) length(unique(x)))

# Überprüfe, ob ein Merkmal zu viele Kategorien hat
too_many_categories <- num_categories > 10

# Gib die Namen der Merkmale aus, die zu viele Kategorien haben
columns_to_remove <- colnames(train_no_numeric)[too_many_categories]


# Ermittle die Spaltennamen, die behalten werden sollen
keep_columns <- setdiff(colnames(final_df), columns_to_remove)

# Erstelle ein Subset des Dataframes mit den behaltenen Spaltennamen
final_df_simplified <- final_df[, keep_columns]

columns_to_remove
[1] "date"                   "issued"                 "district_name"          "district_name.accounts"

Nun muss der Datensatz noch auf NA’s überprüft werden.

# Count the number of NA values in the data frame
num_na <- sum(is.na(final_df_simplified))

# Print the total number of NA values
print(paste("Total number of NA values:", num_na))
[1] "Total number of NA values: 70"
# Create a logical vector indicating whether each element is NA
na_matrix <- is.na(final_df_simplified)

# Sum the number of NA values per row
na_counts <- rowSums(na_matrix)

# Count the rows with NA values
num_na_rows <- sum(na_counts > 0)

# Print the number of rows with NA values
print(paste("Number of rows with NA values:", num_na_rows))
[1] "Number of rows with NA values: 18"
# Sum the number of NA values per column
na_counts_cols <- colSums((na_matrix))

# Count the columns with NA values
num_na_cols <- sum(na_counts_cols > 0)

# Print the number of columns with NA values
print(paste("Number of columns with NA values:", num_na_cols))
[1] "Number of columns with NA values: 4"

Fast jede Zeile hat irgendwo ein NA. Es sind auch viele Kolonnen betroffen. Wir können also nicht alle Observationen oder Variabeln mit NA’s entfernen, da sonst der Datenverlust sehr gross wäre. Daher imputieren wir die numerischen fehlenden Werte mit dem Median und die fehlenden kategorialen Werte mit dem Wert, welcher am meisten vorkommt.

# Impute NA numbers with the median
final_df_simplified <- final_df_simplified %>% 
  mutate_if(is.numeric, list(~ if_else(is.na(.), median(., na.rm = TRUE), as.double(.))))

# Impute NA strings/factors with the most common value
final_df_simplified <- final_df_simplified %>% 
  mutate_if(is.character, list(~ if_else(is.na(.), mode(.), .)))

Wir überprüfen nochmals, ob es keine fehlenden Werte mehr gibt.

sum(is.na(final_df_simplified))
[1] 0

Da das Kaufdatum für den Split benötigt wird aber vorher entfernt wurde, da es mehr als 10 verschiedene Ausprägungen annehmen kann, fügen wir es hier nochmals zum neuen Datensatz hinzu.

final_df_simplified$issued <- final_df$issued
splits <- split_data(final_df_simplified, test_size = 0.2)
train <- splits$train
test <- splits$test
train_sets <- splits$train_sets
val_sets <- splits$val_sets
regression_all_metrics <- list()
regression_all_predictions <- list()
regression_all_predictions_threshold <- list()

for(i in 1:length(val_sets)) {
  
  regression_all <- glm(has_card ~ ., data = train_sets[[i]], family = binomial)
  
  regression_all_predictions[[i]] <- predict(regression_all, val_sets[[i]], type = "response")
  
  threshold <- 0.5
  
  regression_all_predictions_threshold[[i]] <- ifelse(regression_all_predictions[[i]] > threshold, TRUE, FALSE)

  regression_all_metrics[[i]] <- get_metrics(regression_all_predictions_threshold[[i]], val_sets[[i]]$has_card, "Logistic Regression All")
  
}
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning in predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
  prediction from a rank-deficient fit may be misleading
regression_all_metrics <- do.call("rbind", regression_all_metrics)
metrics_all <- bind_rows(metrics_all, regression_all_metrics)

colMeans(regression_all_metrics %>% select(-model))
 accuracy     kappa  matthews precision    recall        f1 
0.7796460 0.7781299 0.5616727 0.7869296 0.7736214 0.7785520 

#regression_all_explainer <- explain(regression_all, data = train)
#plot_confusion_matrix(regression_all_predictions_threshold, test$has_card)
#make_roc_plot_and_get_auc(regression_all_predictions, test$has_card)
#plot_feature_importance(regression_all)

Decision Tree

Für den Decison Tree verwenden wir das Package “rpart”.

decision_tree_metrics <- list()
decision_tree_predictions <- list()
decision_tree_predictions_threshold <- list()

for(i in 1:length(val_sets)) {
  
  decision_tree <- rpart(has_card ~ ., data = train_sets[[i]], method = "class")
  
  decision_tree_predictions[[i]] <- predict(decision_tree, val_sets[[i]], type = "prob")[, 2]
  
  threshold <- 0.5
  
  decision_tree_predictions_threshold[[i]] <- ifelse(decision_tree_predictions[[i]] > threshold, TRUE, FALSE)

  decision_tree_metrics[[i]] <- get_metrics(decision_tree_predictions_threshold[[i]], val_sets[[i]]$has_card, "Decision Tree")
  
}

decision_tree_metrics <- do.call("rbind", decision_tree_metrics)
metrics_all <- bind_rows(metrics_all, decision_tree_metrics)

colMeans(decision_tree_metrics %>% select(-model))
 accuracy     kappa  matthews precision    recall        f1 
0.8486726 0.8475432 0.7034824 0.8136636 0.9082925 0.8574300 
#plot_confusion_matrix(decision_tree_predictions_threshold, test$has_card)
#make_roc_plot_and_get_auc(decision_tree_predictions, test$has_card)
#predictions_train <- predict(decision_tree, train, type = "prob")[, 2]
#best_threshold <- find_best_threshold(predictions_train, train$has_card)
#rpart.plot(decision_tree)

Die Klassifikation ist um einiges besser auf dem Decision Tree. Es scheint also für unseren Verwendungszweck der bessere Algorithmus zu sein. Wir untersuchen noch Erweiterungen des Decision Trees: der Random Forest.

Random Forest

Für den Random Forest brauchen wir das Package “randomForest”. Es verwendet Breiman’s random forest algorithmus und kann für Klassifikation und Regression verwendet werden.

Beim Random Forest muss unsere Zielvariabel noch in einen Faktor umgewandelt werden, damit die Wahrscheinlichkeiten vorhergesagt werden können.

random_forest_metrics <- list()
random_forest_predictions <- list()
random_forest_predictions_threshold <- list()

for(i in 1:length(val_sets)) {
  
  train_sets[[i]]$has_card <- as.factor(train_sets[[i]]$has_card)
  
  random_forest <- randomForest(has_card ~ ., data = train_sets[[i]], method = "class")
  
  random_forest_predictions[[i]] <- predict(random_forest, val_sets[[i]], type = "prob")[, 2]
  
  threshold <- 0.5
  
  random_forest_predictions_threshold[[i]] <- ifelse(random_forest_predictions[[i]] > threshold, TRUE, FALSE)

  random_forest_metrics[[i]] <- get_metrics(random_forest_predictions_threshold[[i]], val_sets[[i]]$has_card, "Random Forest")
  
}

random_forest_metrics <- do.call("rbind", random_forest_metrics)
metrics_all <- bind_rows(metrics_all, random_forest_metrics)

colMeans(random_forest_metrics %>% select(-model))
 accuracy     kappa  matthews precision    recall        f1 
0.8592920 0.8582309 0.7280141 0.8135361 0.9359801 0.8695606 

Der Random Forest ist ein wenig besser als der Decision Tree. Als nächstes wollen wir probieren, ob eine Hyperparameteroptimierung unser Resultat noch verbessern kann.

#plot_confusion_matrix(random_forest_predictions_threshold, test$has_card)
#make_roc_plot_and_get_auc(random_forest_predictions, test$has_card)
#predictions_train <- predict(random_forest, train, type = "prob")[, 2]
#best_threshold <- find_best_threshold(predictions_train, train$has_card)

XGBoost

Das XGBoost Modell wird mit der Library “xgboost” trainiert. Da es Probleme mit nicht-numerischen Variabeln gab, wurden diese entfernt.

xg_boost_metrics <- list()
xg_boost_predictions <- list()
xg_boost_predictions_threshold <- list()

for(i in 1:length(val_sets)) {
  
  train_data <- train_sets[[i]]
  test_data <- val_sets[[i]]
  
  train_data[] <- lapply(train_sets[[i]], as.numeric)
  test_data[] <- lapply(val_sets[[i]], as.numeric)

  
  train_data <- cbind(as.numeric(train_sets[[i]]$has_card), train_data)
  test_data <- cbind(as.numeric(val_sets[[i]]$has_card), test_data)
  
  train_preds <- as.matrix(sapply(train_data[,-1], as.numeric))
  train_labels <- train_data[, 1]
  
  test_preds <- as.matrix(sapply(test_data[,-1], as.numeric))
  test_labels <- test_data[, 1]
  
  param <- list(max_depth = 100, eta = 0.1, nthread = 2)

  # Train the model
  xg_boost <- xgboost(data = train_preds, label = train_labels - 1, nrounds = 100, objective = "binary:logistic", verbose = 0, maximize = "precision", params = param)

  # Make predictions on the test data
  xg_boost_predictions[[i]] <- predict(xg_boost, test_preds) 
  
  threshold <- 0.5
  
  xg_boost_predictions_threshold[[i]] <- ifelse(xg_boost_predictions[[i]] > threshold, TRUE, FALSE)
  
  xg_boost_metrics[[i]] <- get_metrics(xg_boost_predictions_threshold[[i]], val_sets[[i]]$has_card, "XGBoost")
  
}
Warning in lapply(train_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(train_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(train_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(train_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(train_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(val_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(val_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(val_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(val_sets[[i]], as.numeric) :
  NAs introduced by coercion
Warning in lapply(val_sets[[i]], as.numeric) :
  NAs introduced by coercion
Error in `[.default`(confusion_matrix, 2, 2) : subscript out of bounds

TODO: CHECK if ifelse is correct

Hyperparamter Optimierung auf Random Forest

Recursive Feature Elimnation

Eine Möglichkeit, die Leistung von Random Forest zu verbessern, ist die Verwendung von Recursive Feature Elimination (RFE).

RFE ist ein Feature Selection-Verfahren, das dazu verwendet wird, die wichtigsten Features (also diejenigen Merkmale, die für die Vorhersage am wichtigsten sind) auszuwählen und alle anderen zu entfernen. Dies hat mehrere Vorteile:

Es reduziert die Laufzeit von Random Forest, da weniger Features verarbeitet werden müssen. Es kann dazu beitragen, Overfitting zu vermeiden, indem es irrelevanten oder redundanten Features entfernt. Es kann dazu beitragen, die Interpretierbarkeit von Random Forest zu verbessern, da wichtigere Features leichter zu verstehen sind.

Hyperparameteroptimierung

Übersicht über alle Modelle

Fazit

Mehrwert des Modells in der Praxis

Bei unserem Model handelt es sich um ein Product Affinity Model. Es soll voraussagen, ob eine Person (hier eine Kund:in der Bank) ein bestimmtes Produkt kauft (hier eine Kreditkarte) oder nicht. Ausserdem gibt unser Modell auch an, mit welcher Wahrscheinlichkeit sie diese kauft. Dies könnte von grossem Mehrwert für eine Bank sein, da sie anhand des Modells die Marketing-Strategie optimieren und ihre Ressourcen gezielter einsetzen können.

Es kann Banken helfen, ihre Marketing-Bemühungen besser zu segmentieren, indem es zeigt, wer wahrscheinlich an einer Kreditkarte interessiert ist und wer nicht. Dies kann dazu beitragen, die Conversion-Rate zu verbessern und die Kosten für Marketing-Aktionen zu senken.

Ein weiterer Vorteil, den ein Product Affinity Model bieten kann, ist, dass es Banken helfen kann, ihre Kundenbeziehungen zu verbessern, indem es ihnen ermöglicht, personalisierte Angebote und Services anzubieten.

Wenn das Modell vorhersagt, wer wahrscheinlich eine Kreditkarte kaufen wird, können Banken ihren Kunden personalisierte Angebote und Services anbieten, die auf ihre individuellen Bedürfnisse und Vorlieben abgestimmt sind. Dies kann dazu beitragen, die Kundenzufriedenheit zu erhöhen und die Kundenbindung zu stärken. Dafür müsste das Modell aber noch erweitert werden.

Ein personalisiertes Angebot könnte beispielsweise eine Kreditkarte sein, die speziell auf die Bedürfnisse und Vorlieben eines Kunden abgestimmt ist, z.B. mit einem hohen Rückzahlungsprozentsatz für bestimmte Kategorien von Einkäufen oder mit Reiseversicherungen und anderen Vorteilen, die für den Kunden besonders wichtig sind.

Indem Banken ihren Kunden personalisierte Angebote und Services anbieten, können sie ihre Beziehungen zu ihnen stärken und ihre Zufriedenheit erhöhen.

LS0tDQp0aXRsZTogImFtbCINCnN1YnRpdGxlOiAiTWluaS1DaGFsbGVuZ2UgMSINCmF1dGhvcjogIlBhc2NhbCBCZXJnZXIgdW5kIFJhcGhhZWwgU3RyZWJlbCINCmRhdGU6ICIxOC4gT2t0b2JlciAyMDIyIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogNA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQ0KICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICAgIHRoZW1lOiB1bml0ZWQNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NClItVmVyc2lvbjogKipbRGVmYXVsdF0gWzY0LWJpdF0gQzpcXFByb2dyYW0gRmlsZXNcXFJcXFItNC4xLjAqKg0KDQojIEltcG9ydHMNCg0KYGBge3IgZWNobz1GQUxTRSwgY2FjaGU9RkFMU0UsIHJlc3VsdHM9RkFMU0UsIGNvbW1lbnQ9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIGNsZWFyIGVudmlyb25tZW50DQpybShsaXN0ID0gbHMoKSkNCg0KIyBuw7Z0aWdlIFBhY2tldGUNCnBhY2thZ2VzIDwtIGMoInRpZHl2ZXJzZSIsICJkYXRhLnRhYmxlIiwgInRpZHltb2RlbHMiLCAibHVicmlkYXRlIiwgImNhcmV0IiwgInBST0MiLCAicmFuZG9tRm9yZXN0IiwgInJwYXJ0IiwgInJwYXJ0LnBsb3QiLCAieGdib29zdCIsICJEQUxFWCIpDQoNCiMgTm9jaCBuaWNodCBpbnN0YWxsaWVydGUgUGFrZXRlIGluc3RhbGxpZXJlbg0KaW5zdGFsbGVkX3BhY2thZ2VzIDwtIHBhY2thZ2VzICVpbiUgcm93bmFtZXMoaW5zdGFsbGVkLnBhY2thZ2VzKCkpDQoNCmlmIChhbnkoaW5zdGFsbGVkX3BhY2thZ2VzID09IEZBTFNFKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKHBhY2thZ2VzWyFpbnN0YWxsZWRfcGFja2FnZXNdKQ0KfQ0KIyBMYWRlbiBkZXIgUGFja2V0ZQ0KaW52aXNpYmxlKGxhcHBseShwYWNrYWdlcywgbGlicmFyeSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkNCg0KIyBjaGFuZ2Ugb3B0aW9ucw0Kb3B0aW9ucyhkcGx5ci5zdW1tYXJpc2UuaW5mb3JtID0gRkFMU0UpDQoNCnNldC5zZWVkKDI3KQ0KYGBgDQoNCkRpZSBEYXRlbiBoYWJlbiB3aXIgdm9uIGZvbGdlbmRlciBRdWVsbGUgYmV6b2dlbiB1bmQgd2VyZGVuIGF1Y2ggYXVmIGRpZXNlciBnZW5hdWVyIEJlc2NocmllYmVuOiBodHRwczovL3NvcnJ5LnZzZS5jei9+YmVya2EvY2hhbGxlbmdlL1BBU1QvaW5kZXguaHRtbA0KKGxpbmtlIFNlaXRlIFBLREQnOTkgQ2hhbGxlbmdlID4gRGF0YSA+IEZpbmFuY2lhbCBEYXRhIERlc2NyaXB0aW9uKQ0KDQojIEVpbmxlc2VuIGRlciBEYXRlbg0KDQpEZXIgRGF0ZW5zYXR6IGJlc3RlaHQgYXVzIGFjaHQgdmVyc2NoaWVkZW5lbiBUYWJlbGxlbiwgd2VsY2hlIHRlaWxzIGR1cmNoIEtleXMgbWl0ZWluYW5kZXIgdmVya27DvHBmdCBzaW5kLg0KDQpgYGB7cn0NCnJvb3RfcGF0aCA8LSAiLi94c2VsbGluZ19iYW5raW5nX2RhdGEtMS94c2VsbGluZ19iYW5raW5nX2RhdGEvIg0KDQphY2NvdW50cyA8LSByZWFkLmNzdihwYXN0ZTAocm9vdF9wYXRoLCAiYWNjb3VudC5jc3YiKSwgaGVhZGVyID0gVFJVRSwgc2VwID0gIjsiKQ0KY2FyZHMgPC0gcmVhZC5jc3YocGFzdGUwKHJvb3RfcGF0aCwgImNhcmQuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCmNsaWVudHMgPC0gcmVhZC5jc3YocGFzdGUwKHJvb3RfcGF0aCwgImNsaWVudC5jc3YiKSwgaGVhZGVyID0gVFJVRSwgc2VwID0gIjsiKQ0KZGlzcG9zaXRpb25zIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJkaXNwLmNzdiIpLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiOyIpDQpkaXN0cmljdHMgPC0gcmVhZC5jc3YocGFzdGUwKHJvb3RfcGF0aCwgImRpc3RyaWN0LmNzdiIpLCBzZXAgPSAiOyIpDQpsb2FucyA8LSByZWFkLmNzdihwYXN0ZTAocm9vdF9wYXRoLCAibG9hbi5jc3YiKSwgaGVhZGVyID0gVFJVRSwgc2VwID0gIjsiKQ0Kb3JkZXJzIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJvcmRlci5jc3YiKSwgaGVhZGVyID0gVFJVRSwgc2VwID0gIjsiKQ0KdHJhbnNhY3Rpb25zIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJ0cmFucy5jc3YiKSwgaGVhZGVyID0gVFJVRSwgc2VwID0gIjsiKQ0KYGBgDQoNCiMgQ2xlYW5pbmcNCg0KIyMgQWNjb3VudHMNCmBgYHtyfQ0Kc2FtcGxlX24oYWNjb3VudHMsIDUpDQpgYGANCkRpZSBBY2NvdW50LVRhYmVsbGUgZW50aMOkbHQgdmllciBLb2xvbm5lbjogZGllIEFjY291bnQtSUQsIGRpZSBEaXN0cmljdC1JRCAod2VsY2hlIGF1ZiBkaWUgRGlzdHJpY3QtVGFiZWxsZSB2ZXJ3ZWlzdCksIGRpZSBGcmVxdWVueiwgd2VsY2hlIGRpZSBIw6R1Zmlna2VpdCBkZXIgQXVzc3RlbGx1bmcgZGVyIEFicmVjaG51bmdlbiBhbHMgS2F0ZWdvcmllIGJlc2FndCwgdW5kIGRhcyBFcnN0ZWxsdW5nc2RhdHVtIGRlcyBBY2NvdW50cy4gRGllIEZyZXF1ZW56IGthbm4gZWluZSB2b24gZHJlaSB2ZXJzY2hpZWRlbmVuIFdlcnRlbiBhbm5laG1lbi4NCg0KYGBge3J9DQp1bmlxdWUoYWNjb3VudHMkZnJlcXVlbmN5KQ0KYGBgDQoNCk5hY2hmb2xnZW5kIHNvbGxlbiBkaWUgRnJlcXVlbnotV2VydGUgw7xiZXJzZXR6dCB1bmQgZGFzIERhdHVtIGluIGVpbiByaWNodGlnZXMgRm9ybWF0IHRyYW5zZm9ybWllcnQgd2VyZGVuLiBBdXNzZXJkZW0gc29sbCBkaWUgVGFiZWxsZSBhdWYgZmVobGVuZGUgV2VydGUgw7xiZXJwcsO8ZnQgd2VyZGVuLg0KDQpgYGB7cn0NCmFjY291bnRzJGRhdGUgPC0gYXMuRGF0ZShhcy5jaGFyYWN0ZXIoYWNjb3VudHMkZGF0ZSksIGZvcm1hdD0gIiV5JW0lZCIpDQoNCmFjY291bnRzJGZyZXF1ZW5jeVthY2NvdW50cyRmcmVxdWVuY3kgPT0gIlBPUExBVEVLIE1FU0lDTkUiXSAgIDwtICJtb250aGx5Ig0KYWNjb3VudHMkZnJlcXVlbmN5W2FjY291bnRzJGZyZXF1ZW5jeSA9PSAiUE9QTEFURUsgVFlETkUiXSAgICAgPC0gIndlZWtseSINCmFjY291bnRzJGZyZXF1ZW5jeVthY2NvdW50cyRmcmVxdWVuY3kgPT0gIlBPUExBVEVLIFBPIE9CUkFUVSJdIDwtICJhZnRlcl90cmFuc2FjdGlvbiINCg0Kc3VtKGlzLm5hKGFjY291bnRzKSkNCmBgYA0KDQpFcyBnaWJ0IGFsc28ga2VpbmUgZmVobGVuZGUgV2VydGUgaW4gZGllc2VtIERhdGFmcmFtZS4NCg0KIyMgQ2FyZHMNCg0KYGBge3J9DQpzYW1wbGVfbihjYXJkcywgNSkNCmBgYA0KRGllIENhcmQtVGFiZWxsZSBlbnRow6RsdCBkaWUgS29sb25uZW4gQ2FyZC1JRCwgRGlzcC1JRCAod2VsY2hlIGF1ZiBkaWUgRGlzcG9zaXRvbi1UYWJlbGxlIHZlcndlaXN0KSwgZGVuIFR5cCBkZXIgS2FydGUgdW5kIGRhcyBBdXNzdGVsbHVuZ3NkYXR1bS4gQXVjaCBoaWVyIG11c3MgZGFzIERhdHVtIHVtZ2V3YW5kZWx0IHdlcmRlbi4gRGVyIHplaXRsaWNoZSBUZWlsIHdpcmQgaWdub3JpZXJ0LCBkYSBlciBpbW1lciAwIGlzdC4gV2VpdGVyIHdlcmRlbiBHb2xkLUthcnRlbiB6dSBub3JtYWxlbiBLYXJ0ZW4gdW1nZXdhbmRlbHQgdW5kIEp1bmlvci1LYXJ0ZW4gZW50ZmVybnQsIGRhIHVuc2VyIE1vZGVsbCBkYXMgS2F1ZmVuIGVpbmVyIG5vcm1hbGVuIEthcnRlIHZvcmhlcnNhZ2VuIHNvbGwuIEF1Y2ggaGllciB3ZXJkZW4gd2llZGVyIGRpZSBmZWhsZW5kZW4gV2VydGUgZ2VwcsO8ZnQuDQoNCmBgYHtyfQ0KY2FyZHMkaXNzdWVkIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKGNhcmRzJGlzc3VlZCksIGZvcm1hdD0gIiV5JW0lZCIpDQoNCmNhcmRzJHR5cGVbY2FyZHMkdHlwZSA9PSAiZ29sZCJdICAgICA8LSAiY2xhc3NpYyINCmNhcmRzIDwtIGZpbHRlcihjYXJkcywgdHlwZSA9PSAiY2xhc3NpYyIpDQoNCnN1bShpcy5uYShjYXJkcykpDQpgYGANCg0KRXMgZ2lidCBrZWluZSBmZWhsZW5kZSBXZXJ0ZSwgd2VsY2hlIHdlaXRlcmUgQWt0aW9uZW4gZXJmb3JkZXJuIHfDvHJkZW4uDQoNCiMjIENsaWVudHMNCg0KYGBge3J9DQpzYW1wbGVfbihjbGllbnRzLCAxMCkNCmBgYA0KDQpEaWUgVGFiZWxsZSBDbGllbnRzIGVudGjDpGx0IGRpZSBDbGllbnQtSUQsIGRhcyBHZWJ1cnRzZGF0dW0gdW5kIGRpZSBEaXN0cmljdC1JRCAod2VsY2hlIGF1ZiBkaWUgRGlzdHJpY3QtVGFiZWxsZSB2ZXJ3ZWlzdCkuIERlciBTcGFsdGUgR2VidXJ0c2RhdHVtIHNpZWh0IG1hbiBhdWYgZGVuIGVyc3RlbiBCbGljayBkaWUgRGF0dW1zcsOkcHJlc2VudGF0aW9uIG5pY2h0IGFuLiBJbiBkZXIgRG9rdSB3aXJkIGFiZXIgZGllIFN0cnVrdHVyIGVyc2ljaHRsaWNoOiBEYXMgRGF0dW1zZm9ybWF0IGlzdCBmw7xyIE3DpG5uZXIgWVlNTUREIHVuZCBmw7xyIEZyYXVlbiBZWU1NREQrNTBERC4NCg0KSW4gRm9sZ2Ugd2lyZCBkaWUgTnVtbWVyIGluIGlocmUgRGF0dW1zcsOkcHJlc2VudGF0aW9uIGtvbnZlcnRpZXJ0IHVuZCBkaWUgU3BhbHRlICJnZW5kZXIiIGFscyBtYWxlL2ZlbWFsZSBhdWZnZXNjaGzDvHNzZWx0LiBadWRlbSB3aXJkIGRhcyBBbHRlciBkZXIgS3VuZGVuIGltIEphaHIgMTk5OSBiZXJlY2huZXQsIGRhIGRlciBEYXRlbnNhdHogYXVzIGRpZXNlbSBKYWhyIHN0YW1tdC4gQW5zY2hsaWVzc2VuZCBrYW5uIGRhcyBHZWJ1cnRzZGF0dW0gZW50ZmVybnQgd2VyZGVuLg0KDQpBdXNzZXJkZW0gd2VyZGVuIGF1Y2ggaGllciB3aWVkZXIgZGllIGZlaGxlbmRlbiBXZXJ0ZSDDvGJlcnByw7xmdC4NCg0KYGBge3J9DQojIE1vbnRocyBhYm92ZSAxMiBtdXN0IGJlIGZlbWFsZQ0KY2xpZW50cyA8LSBtdXRhdGUoY2xpZW50cywgZ2VuZGVyID0gDQogICAgICAgICAgICAgICAgICAgICBpZmVsc2Uoc3Vic3RyKGJpcnRoX251bWJlciwgMywgNCkgPiAxMiwgImZlbWFsZSIsICJtYWxlIikpDQoNCiMgU3Vic3RyYWN0IHRoZSA1MCB0byBnZXQgdGhlIGJpcnRoIG1vbnRoDQpjbGllbnRzIDwtIG11dGF0ZShjbGllbnRzLCBiaXJ0aF9tb250aCA9DQogICAgICAgICAgICAgICAgICAgICBpZmVsc2UoYXMubnVtZXJpYyhzdWJzdHIoYmlydGhfbnVtYmVyLCAzLCA0KSkgPiAxMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5udW1lcmljKHN1YnN0cihiaXJ0aF9udW1iZXIsIDMsIDQpKSAtIDUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoc3Vic3RyKGJpcnRoX251bWJlciwgMywgNCkpKSkNCg0KIyBUcmFuc2Zvcm0gdGhlIGJpcnRoX251bWJlciB0byBhIGRhdGUNCmNsaWVudHMgPC0gbXV0YXRlKGNsaWVudHMsIGJpcnRoX251bWJlciA9IHBhc3RlKCIxOSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJzdHIoYmlydGhfbnVtYmVyLCAxLCAyKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfcGFkKGJpcnRoX21vbnRoLCAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWQgPSAiMCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vic3RyKGJpcnRoX251bWJlciwgNSwgNiksDQogICAgICAgICAgICAgICAgICAgc2VwID0gIiIsIGNvbGxhcHNlID0gTlVMTCkpDQpjbGllbnRzJGJpcnRoX2RhdGUgPC0gYXMuRGF0ZShhcy5jaGFyYWN0ZXIoY2xpZW50cyRiaXJ0aF9udW1iZXIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1hdD0gIiVZJW0lZCIpDQoNCiMgUmVtb3ZlIHVudXNlZCBjb2x1bW5zDQpjbGllbnRzJGJpcnRoX21vbnRoIDwtIE5VTEwNCmNsaWVudHMkYmlydGhfbnVtYmVyIDwtIE5VTEwNCg0KIyBHZXQgdGhlIGFnZSBvZiB0aGUgY2xpZW50cyBpbiB0aGUgeWVhciAxOTk5IGFuZCBzYXZlIGl0IGluIGEgY29sdW1uDQpnZXRfYWdlIDwtIGZ1bmN0aW9uKGJpcnRoX2RhdGUpIHsNCiAgYmFzZV95ZWFyIDwtIDk5DQogIHllYXIgPC0gc3Vic3RyKGJpcnRoX2RhdGUsIDMsIDQpDQogIHJlc3VsdCA8LSBiYXNlX3llYXIgLSBhcy5pbnRlZ2VyKHllYXIpDQogIA0KICByZXR1cm4ocmVzdWx0KQ0KfQ0KY2xpZW50cyA8LSBjbGllbnRzICU+JQ0KICAgbXV0YXRlKGFnZSA9IGdldF9hZ2UoYmlydGhfZGF0ZSkpDQoNCmNsaWVudHMkYmlydGhfZGF0ZSA8LSBOVUxMDQoNCnN1bShpcy5uYShjbGllbnRzKSkNCmBgYA0KQXVjaCBoaWVyIGdpYnQgZXMga2VpbmUgZmVobGVuZGVuIFdlcnRlLCB3ZWxjaGUgdW50ZXJzdWNodCB3ZXJkZW4gbcO8c3N0ZW4uDQoNCkp1Z2VuZGxpY2hlIHVuZCBQZXJzb25lbiwgd2VsY2hlIHfDpGhyZW5kIGRlcyBaZWl0cmF1bXMgZGVzIERhdGVuc2F0emVzIGVyc3QgZXJ3YWNoc2VuIHdvcmRlbiBzaW5kLCBzb2xsZW4gbmljaHQgaW4gZGllIEF1c3dlcnR1bmcgZWluZmxpZXNzZW4uDQpEYSBzaWNoIGRlciBEYXRlbnNhdHogw7xiZXIgZWluZW4gWmVpdHJhdW0gdm9uIHNlY2hzIEphaHJlbiBlcnN0cmVja3Qgd2VyZGVuIGFsbGUgQ2xpZW50cyBqw7xuZ2VyIGFscyAyNSBKYWhyZSBoZXJhdXNnZWZpbHRlcnQuDQoNCmBgYHtyfQ0KY2xpZW50cyA8LSBjbGllbnRzICU+JSBmaWx0ZXIoYWdlID49IDI1KQ0KYGBgDQoNCiMjIERpc3Bvc2l0aW9ucw0KDQpgYGB7cn0NCnNhbXBsZV9uKGRpc3Bvc2l0aW9ucywgNSkNCmBgYA0KRGllIFRhYmVsbGUgRGlzcG9zdGlvbnMgZW50aMOkbHQgZGllIERpc3Bvc2l0aW9uLUlELCBkaWUgQ2xpZW50LUlEICh3ZWxjaGUgYXVmIGRpZSBUYWJlbGxlIENsaWVudHMgdmVyd2Vpc3QpLCBkaWUgQWNjb3VudC1JRCAod2VsY2hlIGF1ZiBkaWUgVGFiZWxsZSBBY2NvdW50cyB2ZXJ3ZWlzdCkgdW5kIGRlbiBUeXAgZGVyIERpc3Bvc2l0aW9uLiBEaXNwb3NpdGlvbiBzdGVodCBkYWJlaSBmw7xyIGRpZSBSZWNodGUgZWluZXMgS3VuZGVuIGbDvHIgZWluIGdld2lzc2VzIEtvbnRvLiBIaWVyIHNvbGxlbiBudXIgT3duZXJzIHZlcndlbmRldCB3ZXJkZW4sIGRhIGRpZSBBbmFseXNlIG51ciBFaWdlbnTDvG1lciB2b24gS29udGVuIGJlaGFuZGVsbiBzb2xsLiBEYSBkZXIgVHlwZSBkYW5uIGbDvHIgamVkZSBEaXNwb3N0aW9uIGRlciBnbGVpY2hlIGlzdCwga2FubiBkaWVzZSBWYXJpYWJlbCBlbnRmZXJudCB3ZXJkZW4uDQoNCmBgYHtyfQ0KZGlzcG9zaXRpb25zIDwtIGRpc3Bvc2l0aW9ucyAlPiUgZmlsdGVyKHR5cGUgPT0gJ09XTkVSJykNCmRpc3Bvc2l0aW9ucyR0eXBlIDwtIE5VTEwNCg0Kc3VtKGlzLm5hKGRpc3Bvc2l0aW9ucykpDQpgYGANCg0KQXVjaCBoaWVyIGdpYnQgZXMga2VpbmUgZmVobGVuZGVuIFdlcnRlLCB3ZWxjaGUgZ2VuYXVlciB1bnRlcnN1Y2h0IHdlcmRlbiBtw7xzc3Rlbi4NCg0KIyMgRGlzdHJpY3RzDQoNCmBgYHtyfQ0Kc2FtcGxlX24oZGlzdHJpY3RzLCA1KQ0KYGBgDQpEaWUgVGFiZWxsZSBEaXN0cmljdHMgZW50aMOkbHQgZGVtb2dyYXBoaXNjaGUgSW5mb3JtYXRpb25lbiDDvGJlciB2ZXJzY2hpZWRlbmUgR2ViaWV0ZS4gRGllIFNwYWx0ZW5uYW1lbiBzaW5kIGhpZXIgbnVyIG51bW1lcmllcnQgdW5kIG3DvHNzZW4gcmljaHRpZyBiZW5hbm50IHdlcmRlbi4gRGllcyBrw7ZubmVuIHdpciBhbmhhbmQgZGVyIERva3UgbWFjaGVuLg0KDQpgYGB7cn0NCmRpc3RyaWN0cyA8LSByZW5hbWUoZGlzdHJpY3RzLCBkaXN0cmljdF9pZCA9IEExLCBkaXN0cmljdF9uYW1lID0gQTIsIHJlZ2lvbiA9IEEzLCANCiAgICAgICAgICAgICAgICAgICBpbmhhYml0YW50cyA9IEE0LCBtdW5pY2lwYWxpdGllc19pbmhhYml0YW50c19zbWFsbGVyXzQ5OSA9IEE1LCANCiAgICAgICAgICAgICAgICAgICBtdW5pY2lwYWxpdGllc19pbmhhYml0YW50c181MDBfdG9fMTk5OSA9IEE2LCANCiAgICAgICAgICAgICAgICAgICBtdW5pY2lwYWxpdGllc19pbmhhYml0YW50c18yMDAwX3RvXzk5OTkgPSBBNywgDQogICAgICAgICAgICAgICAgICAgbXVuaWNpcGFsaXRpZXNfaW5oYWJpdGFudHNfbGFyZ2VyXzEwMDAwID0gQTgsIGNpdGllcyA9IEE5LCANCiAgICAgICAgICAgICAgICAgICB1cmJhbl9pbmhhYml0YW50c19yYXRpbyA9IEExMCwgYXZlcmFnZV9zYWxhcnkgPSBBMTEsDQogICAgICAgICAgICAgICAgICAgdW5lbXBsb3ltZW50X3JhdGVfOTUgPSBBMTIsIHVuZW1wbG95bWVudF9yYXRlXzk2ID0gQTEzLA0KICAgICAgICAgICAgICAgICAgIGVudHJlcHJlbmV1cnNfcGVyXzEwMDAgPSBBMTQsIGNyaW1lc185NSA9IEExNSwNCiAgICAgICAgICAgICAgICAgICBjcmltZXNfOTYgPSBBMTYpDQoNCnN1bShpcy5uYShkaXN0cmljdHMpKQ0KYGBgDQpBdWNoIGhpZXIgZ2lidCBlcyBrZWluZSBmZWhsZW5kZW4gV2VydGUuDQoNCiMjIFRyYW5zYWN0aW9ucw0KDQpgYGB7cn0NCnNhbXBsZV9uKHRyYW5zYWN0aW9ucywgNSkNCmBgYA0KDQpEaWUgVHJhbnNha3Rpb25zLVRhYmVsbGUgZW50aMOkbHQgZGllIEtvbG9ubmVuIFRyYW5zYWt0aW9ucy1JRCwgQWNjb3VudC1JRCAod2VsY2hlIGF1ZiBkaWUgVGFiZWxsZSBBY2NvdW50cyB2ZXJ3ZWlzdCksIERhdGUgKGRhcyBEYXR1bSBkZXIgVHJhbnNha3Rpb24pLCBUeXBlIChkZW4gVHlwIGRlciBUcmFuc2FrdGlvbiksIE9wZXJhdGlvbiB1bmQga19zeW1ib2wgKHdlaXRlcmUga2F0ZWdvcmlzY2hlIEluZm9ybWF0aW9uZW4gw7xiZXIgZGllIFRyYW5zYWt0aW9uKSwgQW1vdW50IChkZXIgYWJzb2x1dGUgV2VydCBkZXIgVHJhbnNha3Rpb24pLCBCYWxhbmNlIChkZXIgbmV1ZSBLb250b3N0YW5kKSB1bmQgSW5mb3JtYXRpb25lbiDDvGJlciBkaWUgQmFuayB1bmQgZGVuIEFjY291bnQuDQoNCkluIGRlbiBUcmFuc2FrdGlvbmVuIG11c3MgZGFzIERhdHVtIGdlbcOkc3MgRm9ybWF0IFlZTU1ERCBrb252ZXJ0aWVydCB1bmQgZGllIHZlcnNjaGllZGVuZW4gdHNjaGVjaGlzY2hlbiBBdXNkcsO8Y2tlIMO8YmVyc2V0enQgd2VyZGVuLiAiVllEQUoiIGhlaXNzdCDDvGJlcnNldHp0IEF1c2dhYmUsICJWWUJFUiIgRW50bmFobWUuIFdpciDDvGJlcnNldHplbiBoaWVyIGJlaWRlIEJlZ3JpZmZlIG1pdCBkZW0gV2VydCAid2l0aGRyYXdhbCIuDQoNCmBgYHtyfQ0KIyBDaGFuZ2UgZm9ybWF0cw0KdHJhbnNhY3Rpb25zJGRhdGUgPC0gYXMuRGF0ZShhcy5jaGFyYWN0ZXIodHJhbnNhY3Rpb25zJGRhdGUpLCBmb3JtYXQ9ICIleSVtJWQiKQ0KdHJhbnNhY3Rpb25zJGFtb3VudCA8LSBhcy5udW1lcmljKHRyYW5zYWN0aW9ucyRhbW91bnQpDQp0cmFuc2FjdGlvbnMkYmFsYW5jZSA8LSBhcy5udW1lcmljKHRyYW5zYWN0aW9ucyRiYWxhbmNlKQ0KDQojIFRyYW5zbGF0ZSB2YWx1ZXMNCnRyYW5zYWN0aW9ucyR0eXBlW3RyYW5zYWN0aW9ucyR0eXBlID09ICJQUklKRU0iXSA8LSAiaW5jb21lIg0KdHJhbnNhY3Rpb25zJHR5cGVbdHJhbnNhY3Rpb25zJHR5cGUgPT0gIlZZREFKIl0gIDwtICJ3aXRoZHJhd2FsIg0KdHJhbnNhY3Rpb25zJHR5cGVbdHJhbnNhY3Rpb25zJHR5cGUgPT0gIlZZQkVSIl0gIDwtICJ3aXRoZHJhd2FsIg0KDQp0cmFuc2FjdGlvbnMkb3BlcmF0aW9uW3RyYW5zYWN0aW9ucyRvcGVyYXRpb24gPT0gIlZLTEFEIl0gICAgICAgICAgPC0gImNhc2ggY3JlZGl0Ig0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICJQUkVWT0QgWiBVQ1RVIl0gIDwtICJjb2xsZWN0aW9uIg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICJWWUJFUiJdICAgICAgICAgIDwtICJjYXNoIHdpdGhkcmF3YWwiDQp0cmFuc2FjdGlvbnMkb3BlcmF0aW9uW3RyYW5zYWN0aW9ucyRvcGVyYXRpb24gPT0gIlBSRVZPRCBOQSBVQ0VUIl0gPC0gInJlbWl0dGFuY2UiDQp0cmFuc2FjdGlvbnMkb3BlcmF0aW9uW3RyYW5zYWN0aW9ucyRvcGVyYXRpb24gPT0gIlZZQkVSIEtBUlRPVSJdICAgPC0gImNhcmQgd2l0aGRyYXdhbCINCg0KdHJhbnNhY3Rpb25zJGtfc3ltYm9sW3RyYW5zYWN0aW9ucyRrX3N5bWJvbCA9PSAiRFVDSE9EIl0gPC0gInBlbnNpb24iDQp0cmFuc2FjdGlvbnMka19zeW1ib2xbdHJhbnNhY3Rpb25zJGtfc3ltYm9sID09ICJVUk9LIl0gPC0gImludGVyZXN0Ig0KdHJhbnNhY3Rpb25zJGtfc3ltYm9sW3RyYW5zYWN0aW9ucyRrX3N5bWJvbCA9PSAiU0lQTyJdIDwtICJob3VzZWhvbGQiDQp0cmFuc2FjdGlvbnMka19zeW1ib2xbdHJhbnNhY3Rpb25zJGtfc3ltYm9sID09ICJTTFVaQlkiXSA8LSAicGF5bWVudCBzdGF0ZW1lbnQiDQp0cmFuc2FjdGlvbnMka19zeW1ib2xbdHJhbnNhY3Rpb25zJGtfc3ltYm9sID09ICJQT0pJU1RORSJdIDwtICJpbnN1cmFuY2UiDQp0cmFuc2FjdGlvbnMka19zeW1ib2xbdHJhbnNhY3Rpb25zJGtfc3ltYm9sID09ICJTQU5LQy4gVVJPSyJdICA8LSAibmVnX2ludGVyZXN0Ig0KdHJhbnNhY3Rpb25zJGtfc3ltYm9sW3RyYW5zYWN0aW9ucyRrX3N5bWJvbCA9PSAiVVZFUiJdICA8LSAibG9hbl9wYXkiDQoNCnN1bShpcy5uYSh0cmFuc2FjdGlvbnMpKQ0KYGBgDQoNCkluIGRpZXNlciBTcGFsdGUgZ2lidCBlcyBlaW5lIGdyb3NzZSBBbnphaGwgZmVobGVuZGVyIFdlcnRlLiBCZXJlaXRzIGltIFNhbXBsZSBpc3QgZXJzaWNodGxpY2gsIGRhc3MgYmVpIGRlbiBLb2xvbm5lbiBiYW5rLCBrX3N5bWJvbCB1bmQgYWNjb3VudCBmZWhsZW5kZSBXZXJ0ZSBhbHMgTkEgb2RlciBhbHMgbGVlcmVyIHN0cmluZyB2b3Jrb21tZW4uIERlciBBbnRlaWwgZmVobGVuZGVyIFdlcnRlIHNvbGwgYWxzIG7DpGNoc3RlcyB1bnRlcnN1Y2h0IHdlcmRlbi4NCg0KYGBge3J9DQptaXNzaW5nX2JhbmtfcGVyY2VudGFnZSA9IHJvdW5kKHN1bSh0cmFuc2FjdGlvbnMkYmFuayA9PSAnJykgKiAxMDAgLyBkaW0odHJhbnNhY3Rpb25zKVsxXSwgMikNCm1pc3Npbmdfa19zeW1ib2xfcGVyY2VudGFnZSA9IHJvdW5kKHN1bSh0cmFuc2FjdGlvbnMka19zeW1ib2wgPT0gJycpICogMTAwIC8gZGltKHRyYW5zYWN0aW9ucylbMV0sIDIpDQptaXNzaW5nX2FjY291bnRfcGVyY2VudGFnZSA9IHJvdW5kKHN1bShpcy5uYSh0cmFuc2FjdGlvbnMkYWNjb3VudCkpICogMTAwIC8gZGltKHRyYW5zYWN0aW9ucylbMV0sIDIpDQoNCnByaW50KHBhc3RlKCJJbiBkZXIgS29sb25uZSBCYW5rIGZlaGxlbiIsIG1pc3NpbmdfYmFua19wZXJjZW50YWdlLCAiJSBkZXIgV2VydGUuIikpDQpwcmludChwYXN0ZSgiSW4gZGVyIEtvbG9ubmUga19zeW1ib2wgZmVobGVuIiwgbWlzc2luZ19rX3N5bWJvbF9wZXJjZW50YWdlLCAiJSBkZXIgV2VydGUuIikpDQpwcmludChwYXN0ZSgiSW4gZGVyIEtvbG9ubmUgQWNjb3VudCBmZWhsZW4iLCBtaXNzaW5nX2FjY291bnRfcGVyY2VudGFnZSwgIiUgZGVyIFdlcnRlLiIpKQ0KYGBgDQoNCkJlaSBkZW4gS29sb25uZW4gYmFuayB1bmQgYWNjb3VudCBmZWhsZW4gZmFzdCBkcmVpIFZpZXJ0ZWwgZGVyIFdlcnRlLCBiZWkga19zeW1ib2wgYmVpbmFoZSBkaWUgSMOkbGZ0ZS4gRGFoZXIgaGFiZW4gd2lyIHVucyBlbnRzY2hpZWRlbiwgZGllc2UgS29sb25uZW4gdm9tIERhdGFmcmFtZSB6dSBlbnRmZXJuZW4uDQoNCmBgYHtyfQ0KdHJhbnNhY3Rpb25zJGJhbmsgPC0gTlVMTA0KdHJhbnNhY3Rpb25zJGtfc3ltYm9sIDwtIE5VTEwNCnRyYW5zYWN0aW9ucyRhY2NvdW50IDwtIE5VTEwNCmBgYA0KDQpKZXR6dCBzY2hhdWVuIHdpciB1bnMgbm9jaG1hbHMgZGllIGZlaGxlbmRlbiBXZXJ0ZSBhbi4NCg0KYGBge3J9DQpzdW0oaXMubmEodHJhbnNhY3Rpb25zKSkNCmBgYA0KRHVyY2ggZGFzIEVudGZlcm5lbiBkZXIgZHJlaSBTcGFsdGVuIGtvbm50ZW4gYWxsZSBmZWhsZW5kZW4gV2VydGUgZW50ZmVybnQgd2VyZGVuLiBOdW4gYmVzdGVodCBhYmVyIG5vY2ggZGFzIFByb2JsZW0sIGRhc3MgZGVyIGFuZ2VnZWJlbmUgQmV0cmFnIGRlciBUcmFuc2FrdGlvbmVuIGltbWVyIHBvc2l0aXYgaXN0LCBhdWNoIHdlbm4gZGFzIFZlcm3DtmdlbiBzaW5rdC4gV2lyIGdlaGVuIGFsc28gZGF2b24gYXVzLCBkYXNzIGltbWVyIGRlciBhYnNvbHV0ZSBXZXJ0IGluIGRpZXNlcmUgS29sb25uZSBlcmZhc3N0IGlzdC4gRGllcyBtw7xzc2VuIHdpciBub2NoIGJlcmVpbmlnZW4sIHNvIGRhc3MgZGVyIEJldHJhZyBhdWNoIG5lZ2F0aXYgc2VpbiBrYW5uLg0KDQpHbMO8Y2tsaWNoZXJ3ZWlzZSBnaWJ0IGVzIGVpbmUgd2VpdGVyZSBJbmZvcm1hdGlvbiwgd2VsY2hlIHVucyBiZWkgZGllc2VtIFByb2JsZW0gd2VpdGVyaGlsZnQ6IERpZSBWYXJpYWJsZSAidHlwZSIuIA0KDQpgYGB7cn0NCnVuaXF1ZSh0cmFuc2FjdGlvbnMkdHlwZSkNCmBgYA0KDQpFcyBnaWJ0IG51ciB6d2VpIFRyYW5zYWt0aW9uc3R5cGVuOiBpbmNvbWUgdW5kIHdpdGhkcmF3bC4gRGllc2UgZ2ViZW4gYW4sIG9iIEdlbGQgYXVmIGRhcyBLb250byBmbGllc3N0IG9kZXIgZW50bm9tbWVuIHdpcmQuIEJlaSBUcmFuc2FrdGlvbmVuIG1pdCBUeXAgIndpdGhkcmF3YWwiIGthbm4gYWxzbyBkZXIgQmV0cmFnIG5lZ2llcnQgd2VyZGVuLiBXaXIgZsO8Z2VuIGRpZSBuZXVlbiBWYXJpYWJlbG4gImRpZmZlcmVuY2UiIHVuZCAicHJldl9iYWxhbmNlIiBoaW56dS4gU2llIGJlc2NocmVpYmVuIGRlbiBCZXRyYWcgbWl0IGVudHNwcmVjaGVuZGVtIFZvcnplaWNoZW4gdW5kIGRlbiBLb250b3N0YW5kIHZvciBkZXIgVHJhbnNha3Rpb24uDQoNCmBgYHtyfQ0KZGYgPC0gdHJhbnNhY3Rpb25zDQoNCiMgS29udmVydGllcmVuIFNpZSBkYXMgJ2RhdGUnLUZlbGQgaW4gZWluIERhdHVtDQojZGYkZGF0ZSA8LSBhcy5EYXRlKGRmJGRhdGUpDQoNCiMgU29ydGllcmVuIFNpZSBkYXMgRGF0YWZyYW1lIG5hY2ggTnV0emVyIHVuZCBEYXR1bS4gQmVpIGdsZWljaGVtIERhdHVtIHdpcmQgbmFjaCB0cmFuYWN0aW9uX2lkIHNvcnRpZXJ0LiANCmRmIDwtIGRmW29yZGVyKGRmJGFjY291bnRfaWQsIGRmJGRhdGUsIGRmJHRyYW5zX2lkKSwgXQ0KDQojIEdydXBwaWVyZW4gU2llIGRhcyBEYXRhZnJhbWUgbmFjaCBOdXR6ZXINCmRmIDwtIGdyb3VwX2J5KGRmLCBhY2NvdW50X2lkKQ0KDQojIEl0ZXJpZXJlbiBTaWUgw7xiZXIgamVkZW4gTnV0emVyIHVuZCBiZWFyYmVpdGVuIFNpZSBkaWUgVHJhbnNha3Rpb25lbg0KZGYgPC0gZGYgJT4lIA0KICBzdW1tYXJpemUodHJhbnNhY3Rpb25zID0gew0KICAgIA0KICAgICMgRGllIERpZmZlcmVudCBkZXIgVHJhbnNha3Rpb24genVyIFZvcmhlcmlnZW4gd2lyZCBtaXR0ZWxzIFR5cCBhdXNnZWxlc2VuDQogICAgZGlmZmVyZW5jZSA8LSBpZmVsc2UodHlwZSA9PSAid2l0aGRyYXdhbCIsIGFtb3VudCwgYW1vdW50ICogLTEpDQogICAgDQogICAgIyBCYWxhbmNlIGlzdCBkZXIgS29udG9zdGFuZCBuYWNoIGRlciBUcmFuc2FrdGlvbiwgbWl0dGVscyBkaWZmZXJlbmNlIHdpcmQgZGVyIEtvbnRvc3RhbmQgdm9yIFRyYW5zYWt0aW9uIGVybWl0dGVsdC4NCiAgICBwcmV2X2JhbGFuY2UgPC0gYmFsYW5jZSAtIGRpZmZlcmVuY2UNCg0KICAgICMgRXJzdGVsbGVuIFNpZSBkYXMgRGF0YWZyYW1lIG1pdCBkZW4gVHJhbnNha3Rpb25lbiBmw7xyIGplZGVuIE51dHplcg0KICAgIHRyYW5zYWN0aW9uc19kZiA8LSBkYXRhLmZyYW1lKGFtb3VudCwgZGF0ZSwgYmFsYW5jZSwgcHJldl9iYWxhbmNlLCBkaWZmZXJlbmNlKQ0KICAgIHRyYW5zYWN0aW9uc19kZg0KICB9KSAlPiUNCiAgdW5ncm91cCgpDQoNCnRyYW5zYWN0aW9ucyA8LSB1bm5lc3QoZGYsIHRyYW5zYWN0aW9ucykNCg0Kc2FtcGxlX24odHJhbnNhY3Rpb25zLCA1KQ0KYGBgDQoNCiMjIE9yZGVycw0KDQpEaWUgVGFiZWxsZSBPcmRlcnMgZW50aMOkbHQgSW5mb3JtYXRpb25lbiDDvGJlciBlaW5lbiBaYWhsdW5nc2F1ZnRyYWcuIFNpZSBlbnRow6RsbHQgZGllIEtvbG9ubmVuIE9yZGVyLUlELCBBY2NvdW50LUlEICh3ZWxjaGUgYXVmIGRpZSBUYWJlbGxlIEFjY291bnRzIHZlcndlaXN0KSwgQmFuay1UbyAod2VsY2hlIGJlc2NocmVpYnQsIGFuIHdlbGNoZSBCYW5rIGRpZSBaYWhsdW5nIGdlaHQpLCBBY2NvdW50LVRvICh3ZWxjaGUgYXVmIGRpZSBUYWJlbGxlIEFjY291bnRzIHZlcndlaXN0IHVuZCBkZW4gRW1wZsOkbmdlciBkZXIgWmFobHVuZyBhbmdpYnQpLCBBbW91bnQgKGRlbiBCZXRyYWcgZGVyIFphaGx1bmcpIHVuZCBrX3N5bWJvbCAoZGVuIFR5cCBkZXIgVHJhbnNha3Rpb24pLiBEaWUgV2VydGUgaW4gZGVyIFNwYWx0ZSBrX3N5bWJvbCBzb2xsZW4gd2llZGVyIMO8YmVyc2V0enQgd2VyZGVuLiBGZWhsZW5kZSBXZXJ0ZSBzb2xsZW4gaGllciBtaXQgInVua25vd24iIGVyc2V0enQgd2VyZGVuLg0KDQpgYGB7cn0NCnNhbXBsZV9uKG9yZGVycywgNSkNCmBgYA0KDQpgYGB7cn0NCiMgUmVuYW1lIGNvbHVtbiBrX3N5bWJvbA0Kb3JkZXJzIDwtIHJlbmFtZShvcmRlcnMsICJjaGFyYWN0ZXJpemF0aW9uIiA9ICJrX3N5bWJvbCIpIA0KDQojIFRyYW5zbGF0ZSBjb2x1bW4gY2hhcmFjdGVyaXphdGlvbg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25bb3JkZXJzJGNoYXJhY3Rlcml6YXRpb24gPT0gIlNJUE8iXSAgICAgPC0gImhvdXNlaG9sZCINCm9yZGVycyRjaGFyYWN0ZXJpemF0aW9uW29yZGVycyRjaGFyYWN0ZXJpemF0aW9uID09ICJVVkVSIl0gICAgIDwtICJsb2FuIg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25bb3JkZXJzJGNoYXJhY3Rlcml6YXRpb24gPT0gIlBPSklTVE5FIl0gPC0gImluc3VyYW5jZSINCm9yZGVycyRjaGFyYWN0ZXJpemF0aW9uW29yZGVycyRjaGFyYWN0ZXJpemF0aW9uID09ICJMRUFTSU5HIl0gIDwtICJsZWFzaW5nIg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25bb3JkZXJzJGNoYXJhY3Rlcml6YXRpb24gPT0gIiAiXSAgPC0gInVua25vd24iDQpvcmRlcnMkY2hhcmFjdGVyaXphdGlvbltvcmRlcnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiIl0gIDwtICJ1bmtub3duIg0KDQpvcmRlcnMkYW1vdW50IDwtIGFzLm51bWVyaWMob3JkZXJzJGFtb3VudCkNCg0Kc3VtKGlzLm5hKG9yZGVycykpDQpgYGANCg0KQXVjaCBoaWVyIGdpYnQgZXMga2VpbmUgZmVobGVuZGVuIFdlcnRlLg0KDQojIyBMb2Fucw0KDQpgYGB7cn0NCnNhbXBsZV9uKGxvYW5zLCA1KQ0KYGBgDQoNCkRpZSBUYWJlbGxlIExvYW5zIGVudGjDpGx0IEluZm9ybWF0aW9uZW4gw7xiZXIgRGFybGVoZW4gZGVyIHZlcnNjaGllZGVuZW4gQWNjb3VudHMuIERhYmVpIHN0ZWhlbiB1bnMgSW5mb3JtYXRpb25lbiB3aWUgZGFzIERhdHVtLCBkaWUgSMO2aGUsIGRpZSBEYXVlciwgZGVyIEJldHJhZyBkZXIgemFobHVuZ2VuIHVuZCBkZW4gU3RhdHVzIGRlciBEYXJsZWhlbiB6dXIgVmVyZsO8Z3VuZy4gQXVjaCBoaWVyIHNvbGwgd2llZGVyIGRhcyBEYXR1bXNmb3JtYXQgZ2XDpG5kZXJ0IHVuZCBkZXIgU3RhdHVzIGRlciBEYXJsZWhlbiBpbiBlaW5lbiBiZXNzZXIgbGVzYmFyZW4gV2VydCB1bWdld2FuZGVsdCB3ZXJkZW4uIERpZSBJbmZvcm1hdGlvbmVuIMO8YmVyIGRpZSB2ZXJzY2hpZWRlbmVuIFN0YXR1cyBzaW5kIGRlciBEb2t1bWVudGF0aW9uIGRlcyBEYXRlbnNhdHplcyB6dSBlbnRuZWhtZW4uDQoNCmBgYHtyfQ0KbG9hbnMkZGF0ZSA8LSBhcy5EYXRlKGFzLmNoYXJhY3Rlcihsb2FucyRkYXRlKSwgZm9ybWF0PSAiJXklbSVkIikNCmxvYW5zJHBheW1lbnRzIDwtIGFzLm51bWVyaWMobG9hbnMkcGF5bWVudHMpDQpsb2FucyRhbW91bnQgPC0gYXMubnVtZXJpYyhsb2FucyRhbW91bnQpDQoNCiMgTWFrZSBjb2x1bW4gc3RhdHVzIGh1bWFuIHJlYWRhYmxlDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJBIl0gPC0gImZpbmlzaGVkX3BheWVkIg0KbG9hbnMkc3RhdHVzW2xvYW5zJHN0YXR1cyA9PSAiQiJdIDwtICJmaW5pc2hlZF9ub3RfcGF5ZWQiDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJDIl0gPC0gInJ1bm5pbmdfb2siDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJEIl0gPC0gInJ1bm5pbmdfaW5fZGVidCINCg0Kc3VtKGlzLm5hKGxvYW5zKSkNCmBgYA0KDQpBdWNoIGhpZXIgZ2lidCBlcyBrZWluZSBmZWhsZW5kZW4gV2VydGUuDQoNCiMgWnVzYW1tZW5mw7xnZW4gZGVyIERhdGFmcmFtZXMNCg0KQWxzIG7DpGNoc3RlcyBmYXNzZW4gd2lyIGRpZSBEYXRhZnJhbWVzIHp1IGVpbmVtIGdyb3NzZW4gRGF0YWZyYW1lIHp1c2FtbWVuLCBkYW1pdCB3aXIgc3DDpHRlciBNb2RlbGxlIGRhcmF1ZiB0cmFpbmllcmVuIGvDtm5uZW4uIERhYmVpIG3DvHNzZW4gd2lyIMO8YmVycHLDvGZlbiwgb2IgZXMgc2ljaCBiZWkgZGVuIFZlcmJpbmR1bmdlbiBkZXIgdmVyc2NoaWVkZW5lbiBUYWJlbGxlbiB1bSBuIHp1IG4gQmV6aWVodW5nZW4gaGFuZGVsdC4gV2lyIHN0YXJ0ZW4gbWl0IGRlciBUYWJlbGxlIENsaWVudHMgdW5kIGbDvGdlbiBpbW1lciBtZWhyIFRhYmVsbGVuIGhpbnp1Lg0KDQojIyBDbGllbnRzIHVuZCBEaXNwb3NpdGlvbnMNCg0KYGBge3J9DQpwcmludChwYXN0ZSgiQW56YWhsIEt1bmRlbiBpbiBDbGllbnRzOiIsIGRpbShjbGllbnRzKVsxXSkpDQpwcmludChwYXN0ZSgiQW56YWhsIERpc3Bvc2l0aW9uczoiLCBkaW0oZGlzcG9zaXRpb25zKVsxXSkpDQpgYGANCkVzIGdpYnQgYWxzbyB3ZW5pZ2VyIERpc3Bvc2l0aW9ucyBhbHMgQ2xpZW50cy4gQWJlciBnaWJ0IGVzIENsaWVudHMgbWl0IG1laHJlcmVuIERpc3Bvc2l0aW9ucz8NCg0KYGBge3J9DQpsZW5ndGgodW5pcXVlKGRpc3Bvc2l0aW9ucyRjbGllbnRfaWQpKQ0KYGBgDQoNCk5laW4sIGVzIGdpYnQga2VpbmUgQ2xpZW50cyBtaXQgbWVocmVyZW4gRGlzcG9zaXRpb25lbi4gV2lyIGvDtm5uZW4gYWxzbyBkaWUgYmVpZGVuIERhdGFmcmFtZXMgYW5oYW5kIGVpbmVzIElubmVyLUpvaW5zIG1pdGVpbmFuZGVyIHZlcmJpbmRlbiwgdmVybGllcmVuIGRhYmVpIGFiZXIgbWVociBhbHMgNjAwIEt1bmRlbi4gRGllc2UgS3VuZGVuIHNpbmQgZsO8ciB1bnMgc293aWVzbyBpcnJlbGV2YW50LCBkYSBzaWUga2VpbmUgS29udGVuIGJlc2l0emVuLg0KDQpgYGB7cn0NCmZ1bGwgPC0gaW5uZXJfam9pbihjbGllbnRzLCBkaXNwb3NpdGlvbnMsIGJ5ID0gImNsaWVudF9pZCIsIHN1ZmZpeCA9IGMoIi5jbGllbnQiLCAiLmRpc3Bvc2l0aW9ucyIpKQ0KcHJpbnQocGFzdGUoIkFuemFobCBLdW5kZW4gbWl0IERpc3Bvc2l0aW9uZW46IiwgZGltKGZ1bGwpWzFdKSkNCmBgYA0KRXMgYmxlaWJlbiBhbHNvIDM4NTggS3VuZGVuLg0KDQojIyBBY2NvdW50cw0KDQpgYGB7cn0NCnByaW50KHBhc3RlKCJBbnphaGwgQWNjb3VudHM6IiwgZGltKGFjY291bnRzKVsxXSkpDQpgYGANCg0KRXMgZ2lidCBhbHNvIGdsZWljaCB2aWVsZSBBY2NvdW50cyB3aWUgRGlzcG9zaXRpb25lbi4gQXVjaCBoaWVyIHNvbGwgd2llZGVyIMO8YmVycHLDvGZ0IHdlcmRlbiwgb2IgamVkZXIgQWNjb3VudCBnZW5hdSBlaW5lbiBPd25lciBoYXQuDQoNCmBgYHtyfQ0KIyBDb3VudCB0aGUgbnVtYmVyIG9mIGRpc3Bvc2l0aW9ucyBwZXIgY2xpZW50DQphY2NfY291bnRzIDwtIGNvdW50KGFjY291bnRzLCAiY2xpZW50X2lkIikNCg0KIyBDcmVhdGUgYSBzdW1tYXJ5IG9mIHRoZSBjb3VudHMNCnN1bW1hcnkgPC0gdGFibGUoYWNjX2NvdW50cyRuKQ0KDQpzdW1tYXJ5DQpgYGANCkplZGVyIEFjY291bnQgaGF0IGdlbmF1IGVpbmVuIE93bmVyLiBEYXMgRGF0YWZyYW1lIEFjY291bnRzIGthbm4gYWxzbyB6dSB1bnNlcmVtIGdyb3NzZW4gRGF0YWZyYW1lIGhpbnp1Z2Vmw7xndCB3ZXJkZW4uDQoNCmBgYHtyfQ0KZnVsbCA8LSBpbm5lcl9qb2luKGZ1bGwsIGFjY291bnRzLCBieSA9ICJhY2NvdW50X2lkIiwgc3VmZml4ID0gYygiIiwgIi5hY2NvdW50cyIpKQ0KZGltKGZ1bGwpWzFdDQpgYGANClVuc2VyIERhdGFmcmFtZSBoYXQgaW1tZXIgbm9jaCBlaW5lIEzDpG5nZSB2b24gNDUwMC4NCg0KIyMgTG9hbnMNCg0KYGBge3J9DQpwcmludChwYXN0ZSgiQW56YWhsIExvYW5zOiIsIGRpbShsb2FucylbMV0pKQ0KYGBgDQpFcyBnaWJ0IG51ciA2ODIgTG9hbnMuIERhcyBiZWRldXRldCwgZGFzcyBuaWNodCBqZWRlciBLdW5kZSBlaW4gRGFybGVoZW4gaGF0LCB3YXMgU2lubiBlcmdpYnQuIFdlaXRlciBzb2xsIG5vY2ggw7xiZXJwcsO8ZnQgd2VyZGVuLCBvYiBlcyBLdW5kZW4gZ2lidCwgd2VsY2hlIG1laHJlcmUgRGFybGVoZW4gaGFiZW4uDQoNCmBgYHtyfQ0KbGVuZ3RoKHVuaXF1ZShsb2FucyRhY2NvdW50X2lkKSkNCmBgYA0KDQpFcyBnaWJ0IGFsc28ga2VpbmUgS3VuZGVuLCB3ZWxjaGUgbWVocmVyZSBEYXJsZWhlbiBiZXpvZ2VuIGhhYmVuLiBEaWUgTG9hbnMgc29sbGVuIGFuIGRhcyBnZXNhbXRlIERhdGFmcmFtZSBtaXR0ZWxzIExlZnQgSm9pbiBhZ2Vow6RuZ3Qgd2VyZGVuLiBTbyBnaWJ0IGVzIEt1bmRlbiwgd2VsY2hlIGtlaW5lIExvYW5zIGhhYmVuIHVuZCBkaWUgVmFyaWFiZWxuIGRlbiBXZXJ0IE5BIGhhYmVuLiBCZWkgZGllc2VuIHNvbGxlbiBkaWUgZmVobGVuZGVuIG51bWVyaXNjaGVuIFdlcnRlIGR1cmNoIDAgZXJzZXR6dCB1bmQgZGVyIFN0YXR1cyBhdWYgIm5vX2xvYW4iIGdlw6RuZGVydCB3ZXJkZW4uIERhcyBEYXR1bSBkZXMgTG9hbnMgZW50ZmVybmVuIHdpci4NCg0KYGBge3J9DQpmdWxsIDwtIGxlZnRfam9pbihmdWxsLCBsb2FucywgYnkgPSAiYWNjb3VudF9pZCIsIHN1ZmZpeCA9IGMoIiIsICIubG9hbnMiKSkNCmZ1bGwkYW1vdW50IDwtIGlmZWxzZShpcy5uYShmdWxsJGFtb3VudCksIDAsIGZ1bGwkYW1vdW50KQ0KZnVsbCRkdXJhdGlvbiA8LSBpZmVsc2UoaXMubmEoZnVsbCRkdXJhdGlvbiksIDAsIGZ1bGwkZHVyYXRpb24pDQpmdWxsJHBheW1lbnRzIDwtIGlmZWxzZShpcy5uYShmdWxsJHBheW1lbnRzKSwgMCwgZnVsbCRwYXltZW50cykNCmZ1bGwkc3RhdHVzIDwtIGlmZWxzZShpcy5uYShmdWxsJHN0YXR1cyksICJub19sb2FuIiwgZnVsbCRzdGF0dXMpDQpmdWxsJGRhdGUubG9hbnMgPC0gTlVMTA0KYGBgDQoNCiMjIENhcmRzDQoNCmBgYHtyfQ0KcHJpbnQocGFzdGUoIkFuemFobCBDYXJkczoiLCBkaW0oY2FyZHMpWzFdKSkNCmBgYA0KYGBge3J9DQpsZW5ndGgodW5pcXVlKGNhcmRzJGRpc3BfaWQpKQ0KYGBgDQoNCkVzIGdpYnQgNzQ3IEt1bmRlbiBtaXQgS2FydGVuLCBrZWluIEt1bmRlIGhhdCBtZWhyZXJlIEthcnRlbi4gRXMgbWFjaHQgd2llZGVyIFNpbm4sIGRhc3MgbmljaHQgYWxsZSBLdW5kZW4gZWluZSBLYXJ0ZSBoYWJlLiBEZXIgVHlwIGRlciBLYXJ0ZSBpc3QgaW1tZXIgZ2xlaWNoLiBEYWhlciB3aXJkIGRlciBUeXAgZGVyIEthcnRlIGR1cmNoIGVpbmVuIGJvb2xpc2NoZW4gV2VydCAiaGFzX2NhcmQiIGVyc2V0enQuIERpZXMgd2lyZCBhdWNoIHVuc2VyZSBaaWVsdmFyaWFiZWwgaW4gZGVuIE1vZGVsbGVuIHNlaW4uIEF1Y2ggZGllIENhcmQtSUQga2FubiBlbnRmZXJudCB3ZXJkZW4uDQoNCmBgYHtyfQ0KZnVsbCA8LSBsZWZ0X2pvaW4oZnVsbCwgY2FyZHMsIGJ5ID0gImRpc3BfaWQiLCBzdWZmaXggPSBjKCIiLCAiLmNhcmRzIikpDQpgYGANCg0KYGBge3J9DQpoYXNfY2FyZF9mdW5jdGlvbiA8LSBmdW5jdGlvbih4KSB7DQogIGlmIChpcy5uYSh4KSkgew0KICAgIHJldHVybihGQUxTRSkNCiAgfSBlbHNlIHsNCiAgICByZXR1cm4oVFJVRSkNCiAgfQ0KfQ0KDQpmdWxsJGhhc19jYXJkIDwtIHNhcHBseShmdWxsWywgImNhcmRfaWQiXSwgaGFzX2NhcmRfZnVuY3Rpb24pDQpmdWxsIDwtIGZ1bGwgJT4lIHNlbGVjdCgtY2FyZF9pZCwgLXR5cGUpDQpgYGANCg0KIyMgT3JkZXJzDQoNCmBgYHtyfQ0KcHJpbnQocGFzdGUoIkFuemFobCBPcmRlcnM6IiwgZGltKG9yZGVycylbMV0pKQ0KYGBgDQoNCmBgYHtyfQ0KbGVuZ3RoKHVuaXF1ZShvcmRlcnMkYWNjb3VudF9pZCkpDQpgYGANCkVzIGdpYnQgNjQ3MSBPcmRlcnMgZsO8ciAzNzU4IEt1bmRlbi4gRGFzIGJlZGV1dGV0LCBkYXNzIGVzIEt1bmRlbiBnaWJ0LCB3ZWxjaGUgbWVocmVyZSBPcmRlcnMgaGFiZW4uIERpZXNlIEluZm9ybWF0aW9uZW4gbcO8c3NlbiB3aXIgenUgZWluZXIgUm93IHp1c2FtbWVuZmFzc2VuLiANCg0KRGFiZWkgaGFiZW4gd2lyIHVucyBkYWbDvHIgZW50c2NoaWVkZW4sIGRpZSBWYXJpYWJlbG4gYWNjb3VudF90byB1bmQgYW1vdW50IG5pY2h0IGluIGRlbiBHZXNhbXRkYXRlbnNhdHogZWluZmxpZXNzZW4genUgbGFzc2VuLiBEZXIgYW1vdW50IGlzdCBiZXJlaXRzIGluIGRlbiBUcmFuc2FrdGlvbmVuIGluYmVncmlmZmVuLiBBbiB3ZW4gZGllIFphaGx1bmdlbiBnZWhlbiBzb2xsdGVuIGtlaW5lbiBFaW5mbHVzcyBhdWYgdW5zZXIgTW9kZWxsIGhhYmVuLg0KDQpBbHNvIGJsZWliZW4gdW5zIG5vY2ggZGllIFZhcmlhYmVsbiBjaGFyYWN0ZXJpemF0aW9uIHVuZCBiYW5rX3RvLiBadWVyc3Qgc2NoYXVlbiB3aXIgdW5zIGFuLCB3aWUgdmllbGUgdmVyc2NoaWVkZW5lIFdlcnRlIGRpZXNlIGJlaWRlbiBWYXJpYWJlbG4gYW5uZWhtZW4ga8O2bm5lbi4NCg0KYGBge3J9DQp1bmlxdWUob3JkZXJzJGNoYXJhY3Rlcml6YXRpb24pDQpsZW5ndGgodW5pcXVlKG9yZGVycyRjaGFyYWN0ZXJpemF0aW9uKSkNCmBgYA0KYGBge3J9DQp1bmlxdWUob3JkZXJzJGJhbmtfdG8pDQpsZW5ndGgodW5pcXVlKG9yZGVycyRiYW5rX3RvKSkNCmBgYA0KDQpCZWkgZGllc2VuIGJlaWRlbiBWYXJpYWJlbG4gZ2lidCBlcyBudXIgNSBiencuIDEzIHZlcnNjaGllZGVuZSBBdXNwcsOkZ3VuZ2VuLiBXaXIga8O2bm5lbiBhbHNvIGRpZXNlIFdlcnRlIHdpZSBmb2xndCBhbiBkZW4gZ3Jvc3NlbiBEYXRlbnNhdHogYW5mw7xnZW46DQoNCkbDvHIgamVkZSBBdXNwcsOkZ3VuZyBkZXIgU3BhbHRlIGdpYnQgZXMgZWluZSBuZXVlIEtvbG9ubmUuIERpZXNlIEtvbG9ubmUgbmltbXQgZGVuIFdlcnQgZGVyIEFuemFobCBkZXIgT3JkZXJzIG1pdCBkaWVzZXIgY2hhcmFjdGVyaXphdGlvbiBiencuIGJhbmtfdG8gYW4uDQoNCmBgYHtyfQ0KY2hhcmFjdGVyaXphdGlvbnMgPC0gb3JkZXJzICU+JQ0KICBncm91cF9ieShhY2NvdW50X2lkLCBjaGFyYWN0ZXJpemF0aW9uKSAlPiUNCiAgY291bnQoKQ0KDQpjaGFyYWN0ZXJpemF0aW9uc193aWRlIDwtIGNoYXJhY3Rlcml6YXRpb25zICU+JQ0KICBzcHJlYWQoa2V5ID0gY2hhcmFjdGVyaXphdGlvbiwgdmFsdWUgPSBuKQ0KDQpiYW5rX3RvcyA8LSBvcmRlcnMgJT4lDQogIGdyb3VwX2J5KGFjY291bnRfaWQsIGJhbmtfdG8pICU+JQ0KICBjb3VudCgpDQoNCmJhbmtfdG9zX3dpZGUgPC0gYmFua190b3MgJT4lDQogIHNwcmVhZChrZXkgPSBiYW5rX3RvLCB2YWx1ZSA9IG4pDQoNCm9yZGVycyA8LSBtZXJnZShjaGFyYWN0ZXJpemF0aW9uc193aWRlLCBiYW5rX3Rvc193aWRlLCBieSA9ICJhY2NvdW50X2lkIikNCg0Kb3JkZXJzW2lzLm5hKG9yZGVycyldIDwtIDANCmBgYA0KDQpgYGB7cn0NCmZ1bGwgPC0gbGVmdF9qb2luKGZ1bGwsIG9yZGVycywgYnkgPSAiYWNjb3VudF9pZCIpDQpgYGANCg0KRGllIGZlaGxlbmRlbiBXZXJ0ZSBpbiBkaWVzZW4gS29sb25uZW4gbcO8c3NlbiBqZXR6dCBub2NoIG1pdCAwIGVyc2V0enQgd2VyZGVuLiBEZXIgRWluZmFjaGhlaXQgaGFsYmVyIHNldHplbiB3aXIgZGllc2UgV2VydGUgaGFyZGNvZGVkLg0KDQpgYGB7cn0NCmZ1bGwkaG91c2Vob2xkW2lzLm5hKGZ1bGwkaG91c2Vob2xkKV0gPC0gMA0KZnVsbCRpbnN1cmFuY2VbaXMubmEoZnVsbCRpbnN1cmFuY2UpXSA8LSAwDQpmdWxsJGxlYXNpbmdbaXMubmEoZnVsbCRsZWFzaW5nKV0gPC0gMA0KZnVsbCRsb2FuW2lzLm5hKGZ1bGwkbG9hbildIDwtIDANCmZ1bGwkdW5rbm93bltpcy5uYShmdWxsJHVua25vd24pXSA8LSAwDQpmdWxsJEFCW2lzLm5hKGZ1bGwkQUIpXSA8LSAwDQpmdWxsJENEW2lzLm5hKGZ1bGwkQ0QpXSA8LSAwDQpmdWxsJEVGW2lzLm5hKGZ1bGwkRUYpXSA8LSAwDQpmdWxsJEdIW2lzLm5hKGZ1bGwkR0gpXSA8LSAwDQpmdWxsJElKW2lzLm5hKGZ1bGwkSUopXSA8LSAwDQpmdWxsJEtMW2lzLm5hKGZ1bGwkS0wpXSA8LSAwDQpmdWxsJE1OW2lzLm5hKGZ1bGwkTU4pXSA8LSAwDQpmdWxsJE9QW2lzLm5hKGZ1bGwkT1ApXSA8LSAwDQpmdWxsJFFSW2lzLm5hKGZ1bGwkUVIpXSA8LSAwDQpmdWxsJFNUW2lzLm5hKGZ1bGwkU1QpXSA8LSAwDQpmdWxsJFVWW2lzLm5hKGZ1bGwkVVYpXSA8LSAwDQpmdWxsJFdYW2lzLm5hKGZ1bGwkV1gpXSA8LSAwDQpmdWxsJFlaW2lzLm5hKGZ1bGwkWVopXSA8LSAwDQpgYGANCg0KIyMgRGlzdHJpY3RzDQoNCkplZGVyIEt1bmRlIHNvd2llIGplZGVyIEFjY291bnQgaXN0IGVpbmVtIEdlYmlldCB6dWdld2llc2VuLiBadWVyc3Qgw7xiZXJwcsO8ZmVuIHdpciwgb2IgZXMgc2ljaCBkYWJlaSBpbW1lciB1bSBkYXMgc2VsYmUgR2ViaWV0IGhhbmRlbHQuDQoNCmBgYHtyfQ0Kc3VtKGZ1bGwkZGlzdHJpY3RfaWQgIT0gZnVsbCRkaXN0cmljdF9pZC5hY2NvdW50cykNCmBgYA0KNDA5IFBlcnNvbmVuIGhhYmVuIGlocmVuIEFjY291bnQgYWxzbyBpbiBlaW5lbSBhbmRlcmVuIERpc3RyaWt0LCBhbHMgc2llIHp1Z2V3aWVzZW4gc2luZC4gV2lyIG3DvHNzZW4gYWxzbyBkaWUgRGlzdHJpY3QtSW5mb3JtYXRpb25lbiBmw7xyIGJlaWRlIERpc3RyaWN0LUlEJ3MgenV3ZWlzZW4uIERpZXMgbWFjaGVuIHdpciB3aWVkZXIgbWl0IExlZnQtSm9pbnMuDQoNCmBgYHtyfQ0KZnVsbCA8LSBsZWZ0X2pvaW4oZnVsbCwgZGlzdHJpY3RzLCBieSA9ICJkaXN0cmljdF9pZCIpDQoNCmZ1bGwgPC0gbGVmdF9qb2luKGZ1bGwsIGRpc3RyaWN0cywgYnkgPSBjKCJkaXN0cmljdF9pZC5hY2NvdW50cyI9ImRpc3RyaWN0X2lkIiksIHN1ZmZpeCA9IGMoIiIsICIuYWNjb3VudHMiKSkNCmBgYA0KDQpadW0gU2NobHVzcyBrw7ZubmVuIGRpZSB2ZXJzY2hpZWRlbmVuIElEJ3MgZW50ZmVybnQgd2VyZGVuLCBkYSBzaWUgaXJyZWxldmFudCBmw7xyIGRhcyBNb2RlbGwgc2luZCB1bmQgbmljaHQgbWVociBnZWJyYXVjaHQgd2VyZGVuLiBFaW56aWcgZGllIEFjY291bnQtSUQgd2lyZCBpbSBEYXRlbnNhdHogYmVoYWx0ZW4sIGRhbWl0IGRpZSBUcmFuc2FrdGlvbmVuIGhpbnp1Z2Vmw7xndCB3ZXJkZW4ga8O2bm5lbi4NCg0KYGBge3J9DQpmdWxsIDwtIGZ1bGwgJT4lIHNlbGVjdCgtY2xpZW50X2lkLCAtZGlzdHJpY3RfaWQsIC1kaXNwX2lkLCAtZGlzdHJpY3RfaWQuYWNjb3VudHMsIC1sb2FuX2lkKQ0KYGBgDQoNClVuc2VyIHp1c2FtbWVuZ2VzZXR6dGVzIERhdGFmcmFtZSBzaWVodCBudW4gd2llIGZvbGd0IGF1czoNCg0KYGBge3J9DQpmdWxsDQpgYGANCg0KRXIgYmVpbmhhbHRldCAzODU4IEt1bmRlbiBtaXQgNTkgdmVyc2NoaWVkZW5lbiBNZXJrbWFsZW4uDQoNCiMjIFRyYW5zYWN0aW9ucw0KDQpOdW4gbcO8c3NlbiBudXIgbm9jaCBkaWUgVHJhbnNha3Rpb25zZGF0ZW4genVtIERhdGFmcmFtZSBoaW56dWdlZsO8Z3Qgd2VyZGVuLiBEaWVzZSBtw7xzc2VuIGFiZXIgYW5kZXJzIHp1c2FtbWVuZ2VmYXNzdCB3ZXJkZW4sIGRhIGplZGVyIEt1bmRlIHZpZWxlIFRyYW5zYWt0aW9uZW4gaGFiZW4ga2Fubi4gRGllIFRyYW5zYWt0aW9uZW4gd2VyZGVuIGbDvHIgS3JlZGl0a2FydGVua8OkdWZlciB1bmQgTmljaHQtS8OkdWZlciBhZ2dyZWdpZXJ0LCB3ZXNoYWxiIHdpciBkaWVzZSBhbHMgZXJzdGVuIGFuaGFuZCBkZXIgVmFyaWFibGUgImhhc19jYXJkIiB0cmVubmVuLg0KDQpgYGB7cn0NCmNhcmRfYnV5ZXJzIDwtIGZ1bGwgJT4lIGZpbHRlcihoYXNfY2FyZCA9PSBUUlVFKQ0Kbm9uX2J1eWVycyA8LSBmdWxsICU+JSBmaWx0ZXIoaGFzX2NhcmQgPT0gRkFMU0UpDQoNCnByaW50KHBhc3RlKCJBbnphaGwgS8OkdWZlcjoiLCBkaW0oY2FyZF9idXllcnMpWzFdKSkNCnByaW50KHBhc3RlKCJBbnphaGwgTmljaHQtS8OkdWZlcjoiLCBkaW0obm9uX2J1eWVycylbMV0pKQ0KYGBgDQpFcyBnaWJ0IDcwNiBLcmVkaXRrYXJ0ZW5rw6R1ZmVyIHVuZCAzMTUyIE5pY2h0LUvDpHVmZXIuDQoNCiMjIyBBZ2dyZWdhdGlvbiBkZXIgVHJhbnNha3Rpb25lbiBmw7xyIEtyZWRpdGthcnRlbmvDpHVmZXINCg0KQmVpIGRlbiBLcmVkaXRrYXJ0ZW5rw6R1ZmVybiBpc3QgZGVyIFplaXRwdW5rdCBkZXMgS3JlZGl0a2FydGVua2F1ZnMgZ2VnZWJlbi4gRGEgdW5zZXIgTW9kZWxsIFZvcmhlcnNhZ2VuIHNvbGwsIG9iIGVpbiBLdW5kZSBlaW5lIEtyZWRpa2FydGUga2F1ZnQsIGhhYmVuIGRpZSBNb25hdGUgdW5taXR0ZWxiYXIgdm9yIGRlbSBLYXVmemVpdHB1bmt0IGRlbiBncsO2c3N0ZW4gRWluZmx1c3MuIFdpciBlcnN0ZWxsZW4gYWxzbyBmw7xyIGplZGVuIEtyZWRpdGthcnRlbmvDpHVmZXIgZWluIFJvbGx1cC1GZW5zdGVyIGbDvHIgZGllIGxldHp0ZW4genfDtmxmIE1vbmF0ZSB2b3IgZGVtIEthdWYgZGVyIEtyZWRpdGthcmUsIG1pbnVzIGVpbmVtIE1vbmF0IExhZy4gRGllc2VuIE1vbmF0IGJlYWNodGVuIHdpciBuaWNodCwgZGEgZGllIEJlYXJiZWl0dW5nIGVpbmVzIEtyZWRpdGthcnRlbmthdWZzIGV0d2EgZGllc2UgWmVpdHNwYW5uZSBkYXVlcnQgdW5kIHNpY2ggZGVyIEt1bmRlIGRlbWVudHNwcmVjaGVuZCBiZXJlaXRzIHZvciBkZW0gS2F1ZmRhdHVtIGbDvHIgZWluZSBLcmVkaXRrYXJ0ZSBpbnRlcmVzc2llcnQgaGF0Lg0KDQpBbHMgRXJzdGVzIHdlcmRlbiBkYWbDvHIgZGllIFRyYW5zYWt0aW9uZW4gdm9uIEt1bmRlbiBoZXJhdXNnZWZpbHRlcnQsIHdlbGNoZSBlaW5lIEtyZWRpdGthcnRlIGhhYmVuLg0KDQpgYGB7cn0NCmFjY291bnRfaWRzIDwtIGNhcmRfYnV5ZXJzJGFjY291bnRfaWQNCmJ1eWVyX3RyYW5zYWN0aW9ucyA8LSB0cmFuc2FjdGlvbnNbdHJhbnNhY3Rpb25zJGFjY291bnRfaWQgJWluJSBhY2NvdW50X2lkcyxdDQpgYGANCg0KRGFzIEtyZWRpdGthcnRlbmthdWZkYXR1bSBzb2xsIHp1IGRlbiBUcmFuc2FrdGlvbmVuIGhpbnp1Z2Vmw7xndCB3ZXJkZW4sIGRhbWl0IGRpZXNlIGbDvHIgamVkZW4gS3VuZGVuIGVpbnplbG4gZ2VmaWx0ZXJ0IHdlcmRlbiBrw7ZubmVuLg0KDQpgYGB7cn0NCmJ1eWVyX3RyYW5zYWN0aW9ucyA8LSBtZXJnZShidXllcl90cmFuc2FjdGlvbnMsIGZ1bGxbLCBjKCJhY2NvdW50X2lkIiwgImlzc3VlZCIpXSwgYnk9ImFjY291bnRfaWQiKQ0KYGBgDQoNCk51biBzb2xsZW4gVHJhbnNha3Rpb25lbiBzbyBnZWZpbHRlcnQgd2VyZGVuLCBkYXNzIG51ciBub2NoIFRyYW5zYWt0aW9uZW4gendpc2NoZW4gMTMgTW9uYXRlbiB1bmQgMSBNb25hdCB2b3IgZGVtIEFuZmFuZyBkZXMgTW9uYXRzIGRlcyBLcmVkaXRrYXJ0ZW5rYXVmZXMgdm9ya29tbWVuLg0KDQpgYGB7cn0NCmZpbHRlcl90aW1lX3JhbmdlIDwtIGZ1bmN0aW9uKGRmKQ0Kew0KICBmaWx0ZXJlZF9kZiA8LSBkZiAlPiUNCiAgICBmaWx0ZXIoZGF0ZSA+PSBmbG9vcl9kYXRlKGlzc3VlZCAlbS0lIG1vbnRocygxMyksICJtb250aCIpICYNCiAgICAgICAgIGRhdGUgPD0gKGZsb29yX2RhdGUoaXNzdWVkICVtLSUgbW9udGhzKDEpLCAibW9udGgiKSAtIGRheXMoMSkpKQ0KICByZXR1cm4oZmlsdGVyZWRfZGYpDQp9DQoNCmZpbHRlcmVkX2RmIDwtIGZpbHRlcl90aW1lX3JhbmdlKGJ1eWVyX3RyYW5zYWN0aW9ucykNCmBgYA0KDQpFcyBleGlzdGllcmVuIG9mdCBNb25hdGUsIGJlaSBkZW5lbiBlaW4gS3VuZGUga2VpbmUgVHJhbnNha3Rpb25lbiBnZW1hY2h0IGhhdC4gRGllcyBsaWVndCBlbnR3ZWRlciBkYXJhbiwgZGFzcyBkZXIgS3VuZGUgbmljaHQgdmllbGUgVHJhbnNha3Rpb25lbiB0w6R0aWd0IG9kZXIgZGllIEtyZWRpdGthcnRlIGJlcmVpdHMgaW0gZXJzdGVuIEphaHIgZ2VrYXVmdCBoYXQuIEluIGRpZXNlbiBNb25hdGVuIHdlcmRlbiBpbiBGb2xnZSBqZXdlaWxzIGVpbmUgbGVlcmUgVHJhbnNha3Rpb24gZWluZ2Vmw7xndCwgd2VsY2hlIGRpZSBCYWxhbmNlIGRlciBsZXR6dGVuIFRyYW5zYWt0aW9uIGRlcyBWb3Jtb25hdHMgdHLDpGd0LiANCg0KYGBge3J9DQppbXB1dGVfbWlzc2luZ19tb250aHMgPC0gZnVuY3Rpb24oZmlsdGVyZWRfZGYpDQp7DQogIGZpbHRlcmVkX2RmJG1vbnRoIDwtIGZvcm1hdChmaWx0ZXJlZF9kZiRkYXRlLCAiJVktJW0iKQ0KICByb3dzX3RvX2ltcHV0ZSA8LSBmaWx0ZXJlZF9kZiAlPiUgZ3JvdXBfYnkoYWNjb3VudF9pZCkgJT4lIGZpbHRlcihuX2Rpc3RpbmN0KG1vbnRoKSAhPSAxMikNCiAgcm93c190b19pbXB1dGUgPC0gcm93c190b19pbXB1dGVbb3JkZXIocm93c190b19pbXB1dGUkYWNjb3VudF9pZCwgcm93c190b19pbXB1dGUkZGF0ZSksIF0NCiAgYWNjb3VudF9pZHMgPC0gdW5pcXVlKHJvd3NfdG9faW1wdXRlJGFjY291bnRfaWQpDQogIHByaW50KHBhc3RlKGxlbmd0aChhY2NvdW50X2lkcyksICJ1bnZvbGxzdMOkbmRpZ2UgQWNjb3VudHMgZ2VmdW5kZW4uIikpDQogIHBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbiA9IDAsIG1heCA9IGxlbmd0aChhY2NvdW50X2lkcyksIHN0eWxlID0gMywgd2lkdGggPSA1MCwgY2hhciA9ICI9IikNCiAgDQogIGZvciAoaSBpbiAxOmxlbmd0aChhY2NvdW50X2lkcykpDQogIHsNCiAgICB1c2VyX3RyYW5zYWN0aW9ucyA8LSByb3dzX3RvX2ltcHV0ZVtyb3dzX3RvX2ltcHV0ZSRhY2NvdW50X2lkID09IGFjY291bnRfaWRzW2ldLF0NCiAgICANCiAgICAjIGdldCBhbGwgbW9udGhzIHRoZSBjbGllbnQgc2hvdWxkIGhhdmUgYSB0cmFuc2FjdGlvbiBpbg0KICAgIG1vbnRoc190YXJnZXQgPC0gc2FwcGx5KDEzOjIsIGZ1bmN0aW9uKHgpIGZvcm1hdCh1c2VyX3RyYW5zYWN0aW9ucyRpc3N1ZWRbMV0gJW0tJSBtb250aHMoeCksICIlWS0lbSIpKQ0KICAgIA0KICAgICMgY3JlYXRlIGR1bW15IHRyYW5zYWN0aW9uIGluIGNhc2UgZmlyc3QgbW9udGggaXMgZW1wdHkNCiAgICBuZXdfdHJhbnNhY3Rpb24gPC0gdXNlcl90cmFuc2FjdGlvbnNbMSxdDQogICAgbmV3X3RyYW5zYWN0aW9uJGRpZmZlcmVuY2UgPSAwDQogICAgDQogICAgIyB1c2UgcHJldl9iYWxhbmNlIGluIGNhc2UgdGhlIGZpcnN0IGVsZW1lbnQgZ2V0cyBpbXB1dGVkDQogICAgbmV3X3RyYW5zYWN0aW9uJGJhbGFuY2UgPC0gbmV3X3RyYW5zYWN0aW9uJHByZXZfYmFsYW5jZQ0KICAgIA0KICAgIGZvciAobW9udGhfdGFyZ2V0IGluIG1vbnRoc190YXJnZXQpDQogICAgew0KICAgICAgbmV3X3RyYW5zYWN0aW9uJGRhdGUgPC0gYXMuRGF0ZShwYXN0ZTAobW9udGhfdGFyZ2V0LCAiLTAxIikpDQogICAgICBuZXdfdHJhbnNhY3Rpb24kbW9udGggPC0gbW9udGhfdGFyZ2V0DQogICAgICAjIGdldCBsYXN0IHRyYW5zYWN0aW9uIG9mIGN1cnJlbnQgbW9udGggZnJvbSB0aGUgY3VycmVudCB1c2VyJ3MgdHJhbnNhY3Rpb25zDQogICAgICBsYXN0X3RyYW5zYWN0aW9uIDwtIHRhaWwodXNlcl90cmFuc2FjdGlvbnMgJT4lIGZpbHRlcihtb250aCA9PSBtb250aF90YXJnZXQpLCBuPTEpDQogICAgICANCiAgICAgIGlmKG5yb3cobGFzdF90cmFuc2FjdGlvbikgPT0gMCkNCiAgICAgIHsNCiAgICAgICAgIyBpbXB1dGUgZW1wdHkgdHJhbnNhY3Rpb24gaW4gbWlzc2luZyBtb250aA0KICAgICAgICBmaWx0ZXJlZF9kZiA8LSByYmluZChmaWx0ZXJlZF9kZiwgbmV3X3RyYW5zYWN0aW9uKQ0KICAgICAgfQ0KICAgICAgZWxzZQ0KICAgICAgew0KICAgICAgICBuZXdfdHJhbnNhY3Rpb24kYmFsYW5jZSA8LSBsYXN0X3RyYW5zYWN0aW9uJGJhbGFuY2UNCiAgICAgICAgbmV3X3RyYW5zYWN0aW9uJHByZXZfYmFsYW5jZSA8LSBuZXdfdHJhbnNhY3Rpb24kYmFsYW5jZQ0KICAgICAgfQ0KICAgIH0NCiAgICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgaSkNCiAgfQ0KICBjbG9zZShwYikNCiAgcmV0dXJuKGZpbHRlcmVkX2RmKQ0KfQ0KDQpmaWx0ZXJlZF9kZiA8LSBpbXB1dGVfbWlzc2luZ19tb250aHMoZmlsdGVyZWRfZGYpDQpgYGANCg0KQmVpIDE2OSBLcmVkaXRrYXJ0ZW5rw6R1ZmVyIGlzdCBrZWluIGtvbXBsZXR0ZXMgMTItTW9uYXRzLVJvbGx1cC1GZW5zdGVyIHZvcmhhbmRlbi4gQmVpIGFsbGVuIHd1cmRlbiBkaWUgZmVobGVuZGVuIE1vbmF0ZSBpbXB1dGllcnQuDQoNCk51biB3ZXJkZW4gZGllc2UgRGF0ZW4gYWdncmVnaWVydC4gRXMgd2lyZCBlaW5lIEdydXBwaWVydW5nIGFuaGFuZCBkZXIgYWNjb3VudF9pZCB1bmQgZGVzIE1vbmF0cyBnZW1hY2h0LiBEaWUgV2VydGUgImRpZmZlcmVuY2UiIHVuZCAiYmFsYW5jZSIgYXVmIHZlcnNjaGllZGVuZSBBcnRlbiBhZ2dyZWdpZXJ0Og0KQXVmIGJlaWRlbiBXZXJ0ZW4gZXJmYXNzZW4gd2lyIGRhcyBNaW5pbXVtLCBkYXMgTWF4aW11bSwgZGVuIER1cmNoc2Nobml0dCwgZGVuIE1lZGlhbiB1bmQgZGllIFN0YW5kYXJkYWJ3ZWljaHVuZy4gQmVpICJiYWxhbmNlIiBlcmZhc3NlbiB3aXIgZGVuIGVyc3RlbiB1bmQgZGVuIGxldHp0ZW4gV2VydCBkZXMgTW9uYXRzIHVuZCBiZWkgImRpZmZlcmVuY2UiIGRpZSBBbnphaGwgcG9zaXRpdmUgdW5kIG5lZ2F0aXZlIERpZmZlcmVuemVuLg0KDQpgYGB7cn0NCmdldF9zdW1tYXJ5X2RmIDwtIGZ1bmN0aW9uKGZpbHRlcmVkX2RmKQ0Kew0KICBzdW1tYXJ5X2RmIDwtIGZpbHRlcmVkX2RmICU+JQ0KICBncm91cF9ieShhY2NvdW50X2lkLCBtb250aCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBtYXhfZGlmZmVyZW5jZSA9IG1heChkaWZmZXJlbmNlKSwNCiAgICBtaW5fZGlmZmVyZW5jZSA9IG1pbihkaWZmZXJlbmNlKSwNCiAgICBtYXhfYmFsYW5jZSA9IG1heChiYWxhbmNlKSwNCiAgICBtaW5fYmFsYW5jZSA9IG1pbihiYWxhbmNlKSwNCiAgICBpbml0aWFsX2JhbGFuY2UgPSBmaXJzdChiYWxhbmNlKSwNCiAgICBlbmRfYmFsYW5jZSA9IGxhc3QoYmFsYW5jZSksDQogICAgbWVhbl9iYWxhbmNlID0gbWVhbihiYWxhbmNlKSwNCiAgICBtZWRpYW5fYmFsYW5jZSA9IG1lZGlhbihiYWxhbmNlKSwNCiAgICBzdGRfYmFsYW5jZSA9IHNkKGJhbGFuY2UpLA0KICAgIG1lYW5fZGlmZmVyZW5jZSA9IG1lYW4oZGlmZmVyZW5jZSksDQogICAgbWVkaWFuX2RpZmZlcmVuY2UgPSBtZWRpYW4oZGlmZmVyZW5jZSksDQogICAgc3RkX2RpZmZlcmVuY2UgPSBzZChkaWZmZXJlbmNlKSwNCiAgICBjb3VudF9wb3NpdGl2ZV9kaWZmZXJlbmNlID0gc3VtKGRpZmZlcmVuY2UgPiAwKSwNCiAgICBjb3VudF9uZWdhdGl2ZV9kaWZmZXJlbmNlID0gc3VtKGRpZmZlcmVuY2UgPCAwKQ0KICApDQogIHN1bW1hcnlfZGYgPC0gc3VtbWFyeV9kZiAlPiUgYXJyYW5nZShhY2NvdW50X2lkKQ0KICByZXR1cm4oc3VtbWFyeV9kZikNCn0NCg0Kc3VtbWFyeV9kZiA8LSBnZXRfc3VtbWFyeV9kZihmaWx0ZXJlZF9kZikNCmBgYA0KDQpEaWVzZSBadXNhbW1lbmZhc3N1bmcgc2llaHQgbnVuIGbDvHIgamVkZW4gS3VuZGVuIGZvbGdlbmRlcm1hc3NlbiBhdXM6DQoNCmBgYHtyfQ0Kc3VtbWFyeV9kZiAlPiUgZmlsdGVyKGFjY291bnRfaWQgPT0gNykNCmBgYA0KDQpGw7xyIGplZGVuIEt1bmRlbiBnaWJ0IGVzIG51biBmw7xyIGplZGVuIE1vbmF0IHZvciBkZW0gS3JlZGl0a2FydGVua2F1ZiBlaW5lIFJvdyBtaXQgZGVuIGFnZ3JlZ2llcnRlbiBEYXRlbiBkZXMgTW9uYXRzLiBXaXIgw7xiZXJwcsO8ZmVuIG5vY2gga3Vyeiwgb2IgYXVjaCB3aXJrbGljaCBmw7xyIGplZGVuIEt1bmRlbiB6d8O2bGYgTW9uYXRlIHZvcmhhbmRlbiBzaW5kLg0KDQpgYGB7cn0NCmdldF9pbmNvbXBsZXRlX2J1eWVycyA8LSBmdW5jdGlvbihzdW1tYXJ5X2RmKQ0Kew0KICAjIEtvbnRyb2xsZSwgb2IgZsO8ciBqZWRlbiBhY2NvdW50X2lkIDEyIG1vbmF0ZSB2b3JoYW5kZW4gc2luZA0KICBtb250aF9jb3VudHMgPC0gc3VtbWFyeV9kZiAlPiUNCiAgICAgIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JQ0KICBzdW1tYXJpc2UobW9udGhfY291bnQgPSBuX2Rpc3RpbmN0KG1vbnRoKSkNCg0KICAjIFByw7xmZSwgb2IgamVkZXMgYWNjb3VudF9pZCAxMiBNb25hdGUgaGF0DQogIG1vbnRoX2NvdW50cyA8LSBtb250aF9jb3VudHMgJT4lIGZpbHRlcihtb250aF9jb3VudCAhPSAxMikNCiAgcmV0dXJuKG1vbnRoX2NvdW50cykNCn0NCg0KbW9udGhfY291bnRzIDwtIGdldF9pbmNvbXBsZXRlX2J1eWVycyhzdW1tYXJ5X2RmKQ0KcHJpbnQocGFzdGUoIkFuemFobCBLdW5kZW4gbWl0IHdlbmlnZXIgYWxzIDEyIE1vbmF0ZW46IiwgZGltKG1vbnRoX2NvdW50cylbMV0pKQ0KYGBgDQoNCkplZGVyIEtyZWRpdGthcnRlbmvDpHVmZXIgaGF0IGFsc28gd2lya2xpY2ggMTIgTW9uYXRlLiBBbHMgbsOkY2hzdGVzIHdvbGxlbiB3aXIgZGllc2UgenfDtmxmIFJvd3MgcHJvIEt1bmRlIHp1IGVpbmVyIFJvdyB1bXdhbmRlbG4uIERhZsO8ciBudW1tZXJpZXJlbiB3aXIgenVuw6RjaHN0IGRpZSBNb25hdGUgcHJvIEtyZWRpdGthcnRlbmvDpHVmZXIgdm9uIDEgYmlzIDEyIGR1cmNoLiBEYWJlaSBpc3QgZGVyIE1vbmF0IDEyIGRlciBsZXR6dGUgTW9uYXQgdm9yIGRlbSBLYXVmZGF0dW0gKGFiZ2VzZWhlbiB2b20gTGFnLU1vbmF0KS4NCg0KYGBge3J9DQpzZXRfbW9udGhfaWRzIDwtIGZ1bmN0aW9uKHN1bW1hcnlfZGYpDQp7DQogICMgU29ydGllcmVuIG5hY2ggYWNjb3VudF9pZCB1bmQgTW9uYXQNCiAgc3VtbWFyeV9kZiA8LSBzdW1tYXJ5X2RmW29yZGVyKHN1bW1hcnlfZGYkYWNjb3VudF9pZCwgcmV2KHN1bW1hcnlfZGYkbW9udGgpKSxdDQogIA0KICAjIEhpbnp1ZsO8Z2VuIGRlciBNb25hdHNudW1tZXINCiAgc3VtbWFyeV9kZiRncm91cF9pZCA8LSBhdmUoc2VxX2Fsb25nKHN1bW1hcnlfZGYkYWNjb3VudF9pZCksIHN1bW1hcnlfZGYkYWNjb3VudF9pZCwgRlVOID0gZnVuY3Rpb24oeCkge3h9KQ0KICBzdW1tYXJ5X2RmJG1vbnRoX251bWJlciA8LSAxMg0KICANCiAgZm9yIChpIGluIDI6bnJvdyhzdW1tYXJ5X2RmKSkgew0KICAgIGlmIChzdW1tYXJ5X2RmJGFjY291bnRfaWRbaV0gIT0gc3VtbWFyeV9kZiRhY2NvdW50X2lkW2ktMV0pIHsNCiAgICAgIHN1bW1hcnlfZGYkbW9udGhfbnVtYmVyW2ldIDwtIDEyDQogICAgfSBlbHNlIHsNCiAgICAgIHN1bW1hcnlfZGYkbW9udGhfbnVtYmVyW2ldIDwtIHN1bW1hcnlfZGYkbW9udGhfbnVtYmVyW2ktMV0gLSAxDQogICAgfQ0KICB9DQogIA0KICAjIEVudGZlcm5lIGRpZSBTcGFsdGUgZ3JvdXBfaWQNCiAgc3VtbWFyeV9kZiRncm91cF9pZCA8LSBOVUxMDQogIHN1bW1hcnlfZGYkbW9udGggPC0gTlVMTA0KICByZXR1cm4oc3VtbWFyeV9kZikNCn0NCg0Kc3VtbWFyeV9kZiA8LSBzZXRfbW9udGhfaWRzKHN1bW1hcnlfZGYpDQoNCmBgYA0KDQpBbHMgbsOkY2hzdGVzIGJyYXVjaGVuIHdpciBwaXZvdF93aWRlci4gU28gaGFiZW4gd2lyIGplZGUgS2VubnphaGwgenfDtmxmIG1hbCBhbHMgS29sb25uZSwgamVkZXMgTWFsIG1pdCBkZXIgdm9yaGVyIGVyc3RlbGx0ZW4gTW9uYXRzbnVtbWVyIGFscyBTdWZmaXguDQoNCmBgYHtyfQ0KcGl2b3RfYnlfbW9udGggPC0gZnVuY3Rpb24oc3VtbWFyeV9kZikNCnsNCiAgc3VtbWFyeV9kZl93aWRlIDwtIHN1bW1hcnlfZGYgJT4lDQogICAgZ3JvdXBfYnkoYWNjb3VudF9pZCkgJT4lDQogICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG1vbnRoX251bWJlciwNCiAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSBjKG1heF9kaWZmZXJlbmNlLCBtaW5fZGlmZmVyZW5jZSwgbWF4X2JhbGFuY2UsIG1pbl9iYWxhbmNlLCBpbml0aWFsX2JhbGFuY2UsIGVuZF9iYWxhbmNlLCBtZWFuX2JhbGFuY2UsIG1lZGlhbl9iYWxhbmNlLCBzdGRfYmFsYW5jZSwgbWVkaWFuX2JhbGFuY2UsIHN0ZF9iYWxhbmNlLCBtZWFuX2RpZmZlcmVuY2UsIG1lZGlhbl9kaWZmZXJlbmNlLCBzdGRfZGlmZmVyZW5jZSwgY291bnRfcG9zaXRpdmVfZGlmZmVyZW5jZSwgY291bnRfbmVnYXRpdmVfZGlmZmVyZW5jZSkpDQogIHJldHVybihzdW1tYXJ5X2RmX3dpZGUpDQp9DQoNCnN1bW1hcnlfZGZfd2lkZSA8LSBwaXZvdF9ieV9tb250aChzdW1tYXJ5X2RmKQ0KYGBgDQoNCkRhcyBkYXJhdXMgcmVzdWx0aWVyZW5kZSBEYXRhZnJhbWUgc2llaHQgc28gYXVzOg0KDQpgYGB7cn0NCnN1bW1hcnlfZGZfd2lkZQ0KYGBgDQoNCkbDvHIgamVkZW4gS3JlZGl0a2FydGVua8OkdWZlciBnaWJ0IGVzIGpldHp0IGVpbmUgUm93IG1pdCAxNjkgdmVyc2NoaWVkZW5lbiBNZXJrbWFsZW46IERpZSBhY2NvdW50X2lkIHVuZCBkaWUgMTQgYWdncmVnaWVydGVuIEluZm9ybWF0aW9uZW4gw7xiZXIgZGllIFRyYW5zYWt0aWlvbmVuIGbDvHIgYWxsZSAxMiBNb25hdGUuDQoNCkRpZXNlcyBEYXRhZnJhbWUgZsO8Z2VuIHdpciBub2NoIHp1IGRlbiBhbmRlcmVuIEluZm9ybWF0aW9uZW4gZGVyIEtyZWRpdGthcnRlbmvDpHVmZXIgaGluenUuDQoNCmBgYHtyfQ0Kc3VtbWFyeV9kZl9idXllcnMgPC0gbWVyZ2Uoc3VtbWFyeV9kZl93aWRlLCBzdWJzZXQoY2FyZF9idXllcnMpLCBieSA9ICJhY2NvdW50X2lkIikNCmBgYA0KDQojIyMgQWdncmVnYXRpb24gZGVyIFRyYW5zYWt0aW9uZW4gZsO8ciBOaWNodC1Lw6R1ZmVyDQoNCkbDvHIgTmljaHQtS8OkdWZlciBpc3Qga2VpbiBEYXR1bSBkZXMgS3JlZGl0a2FydGVua2F1ZnMgZ2VnZWJlbiwgZGEgc2llIGphIGtlaW5lIGdla2F1ZnQgaGFiZW4uIEVzIHN0ZWxsdCBzaWNoIGFsc28gZGllIEZyYWdlLCB2b24gd2VsY2hlbSBEYXR1bSBhdXMgbWFuIGRhcyBSb2xsdXAtRmVuc3RlciBlcnN0ZWxsdC4NCg0KV2lyIGhhYmVuIHVucyBkYXp1IGVudHNjaGllZGVuLCBmw7xyIGplZGVuIEvDpHVmZXIgZWluZW4gbcO2Z2xpY2hzdCDDpGhubGljaGVuIE5pY2h0LUvDpHVmZXIgenUgZmluZGVuLiBEYWbDvHIgd2VyZGVuIE5pY2h0a8OkdWZlciBtaXQgZ2xlaWNoZW0gR2VzY2hsZWNodCB1bmQgZ2xlaWNoZXIgUmVnaW9uIGdlc3VjaHQuIERhcyBLYXVmZGF0dW0gZGVzIMOkaG5saWNoZW4gS8OkdWZlcnMgd2lyZCBkYW5uIGF1ZiBkZW4gTmljaHQtS8OkdWZlciDDvGJlcnRyYWdlbi4gRWluIHdlaXRlcmVzIEtyaXRlcml1bSBpc3QsIGRhc3MgZGVyIE5pY2h0LUvDpHVmZXIgbWluZGVzdGVucyBlaW5lIFRyYW5zYWt0aW9uIGluIGRlbSBSb2xsdXAtRmVuc3RlciBoYWJlbiBtdXNzLiBKZWRlciBOaWNodC1Lw6R1ZmVyIHdpcmQgbWF4aW1hbCBlaW5tYWwgYWxzIE1hdGNoIGdlbm9tbWVuLCBkYW1pdCBrZWluZSBkb3BwZWx0ZW4gS3VuZGVuIGltIERhdGVuc2F0eiB2b3Jrb21tZW4uDQoNCkR1cmNoIGRpZXNlcyBWb3JnZWhlbiBiZW51dHplbiB3aXIgbnVyIGVpbmVuIFRlaWwgZGVyIE5pY2h0LUvDpHVmZXIsIGhhYmVuIGFiZXIgZGlyZWt0ZSwgw6RobmxpY2hlIFZlcmdsZWljaHNwZXJzb25lbiwgd2VsY2hlIGltIGdsZWljaGVuIFplaXRyYXVtIGtlaW5lIEtyZWRpdGthcnRlbiBnZWthdWZ0IGhhYmVuLiBBdXNzZXJkZW0gYnJpbmd0IGRhcyBWb3JnZWhlbiBhdWNoIGRlbiBWb3J0ZWlsLCBkYXNzIGRpZSBEYXRlbiBmw7xyIGRhcyBNb2RlbGwgYXV0b21hdGlzY2ggQmFsYW5jZWQgc2luZC4gRGllcyBtYWNodCBkYXMgVHJhaW5pZXJlbiBkZXMgTW9kZWxscyBlaW5mYWNoZXIsIGRhIGVzIGRhenUgYmVpdHLDpGd0LCBkYXNzIGRhcyBNb2RlbGwgbmljaHQgQmlhc2VkIGdlZ2Vuw7xiZXIgZGVuIE5pY2h0LUvDpHVmZXJuIHdpcmQuDQoNCg0KYGBge3J9DQpwYiA8LSB0eHRQcm9ncmVzc0JhcihtaW4gPSAwLCBtYXggPSBucm93KGNhcmRfYnV5ZXJzKSwgc3R5bGUgPSAzLCB3aWR0aCA9IDUwLCBjaGFyID0gIj0iKQ0KDQojIEVyc3RlbGxlIGVpbiBsZWVyZXMgRGF0YUZyYW1lICJzaW1pbGFyX25vbl9idXllcnMiDQpzaW1pbGFyX25vbl9idXllcnMgPC0gZGF0YS5mcmFtZSgpDQoNCiMgSXRlcmllcmUgw7xiZXIgamVkZW4gS3VuZGVuIGltIERhdGFGcmFtZSAiYnV5ZXJzIg0KZm9yIChpIGluIDE6bnJvdyhjYXJkX2J1eWVycykpIA0Kew0KICAjIFfDpGhsZSBkZW4gYWt0dWVsbGVuIEt1bmRlbiBhdXMgZGVtIERhdGFGcmFtZSAiYnV5ZXJzIg0KICBjdXJyZW50X2J1eWVyIDwtIGNhcmRfYnV5ZXJzW2ksIF0NCiAgDQogIHNpbWlsYXJfbm9uX2J1eWVyc190ZW1wIDwtIG5vbl9idXllcnMgJT4lIGZpbHRlcihnZW5kZXIgPT0gY3VycmVudF9idXllciRnZW5kZXIgJiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2lvbiA9PSBjdXJyZW50X2J1eWVyJHJlZ2lvbikNCiAgDQogICMgZGFtaXQgbmljaHQgZGVyIGdsZWljaGUgbm9uX2J1eWVyIGRvcHBlbHQgdmVyd2VuZGV0IHdpcmQNCiAgc2ltaWxhcl9ub25fYnV5ZXJzX3RlbXAgPC0gc2ltaWxhcl9ub25fYnV5ZXJzX3RlbXBbIXNpbWlsYXJfbm9uX2J1eWVyc190ZW1wJGFjY291bnRfaWQgJWluJSBzaW1pbGFyX25vbl9idXllcnMkYWNjb3VudF9pZCxdDQogIA0KICAjIGZpbHRlcmUgbm9uLWJ1eWVycyBoZXJhdXMsIHdlbGNoZSBrZWluZSBUcmFuc2FrdGlvbmVuIGltIHJlbGV2YW50ZW4gWmVpdHJhdW0gdm9yIGlzc3VlZCBoYWJlbg0KICBzaW1pbGFyX25vbl9idXllcnNfdGVtcCRpc3N1ZWQgPC0gY3VycmVudF9idXllciRpc3N1ZWQNCiAgbm9uX2J1eWVyX3RyYW5zYWN0aW9ucyA8LSBtZXJnZSh0cmFuc2FjdGlvbnMsIHNpbWlsYXJfbm9uX2J1eWVyc190ZW1wWywgYygiYWNjb3VudF9pZCIsICJpc3N1ZWQiKV0sIGJ5PSdhY2NvdW50X2lkJykNCiAgbm9uX2J1eWVyX2ZpbHRlcmVkIDwtIGZpbHRlcl90aW1lX3JhbmdlKG5vbl9idXllcl90cmFuc2FjdGlvbnMpDQogIG5vbl9idXllcl9pZHMgPC0gdW5pcXVlKG5vbl9idXllcl9maWx0ZXJlZCRhY2NvdW50X2lkKQ0KICANCiAgc2ltaWxhcl9ub25fYnV5ZXJzX3RlbXAgPC0gc2ltaWxhcl9ub25fYnV5ZXJzX3RlbXBbc2ltaWxhcl9ub25fYnV5ZXJzX3RlbXAkYWNjb3VudF9pZCAlaW4lIG5vbl9idXllcl9pZHMsXQ0KICAgICAgICAgICANCiAgaWYobnJvdyhzaW1pbGFyX25vbl9idXllcnNfdGVtcCkgPiAwKQ0KICB7DQogICAgIyBXw6RobGUgZGVuIGFtIGJlc3RlbiBwYXNzZW5kZW4gS3VuZGVuIGF1cyAic2ltaWxhcl9ub25fYnV5ZXJzX3RlbXAiIGF1cw0KICAgIGJlc3RfbWF0Y2hfaW5kZXggPC0gd2hpY2gubWluKGFicyhzaW1pbGFyX25vbl9idXllcnNfdGVtcCRhZ2UgLSBjdXJyZW50X2J1eWVyJGFnZSkpDQogICAgYmVzdF9tYXRjaCA8LSBzaW1pbGFyX25vbl9idXllcnNfdGVtcFtiZXN0X21hdGNoX2luZGV4LCBdDQogICAgDQogICAgc2ltaWxhcl9ub25fYnV5ZXJzIDwtIHJiaW5kKHNpbWlsYXJfbm9uX2J1eWVycywgY29weShiZXN0X21hdGNoKSkNCiAgICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgaSkNCiAgfQ0KICBlbHNlDQogIHsNCiAgICBwcmludChwYXN0ZTAoY3VycmVudF9idXllciRhY2NvdW50X2lkLCAiOiBubyBub24tYnV5ZXIgZm91bmQiKSkNCiAgfQ0KfQ0KDQpjbG9zZShwYikNCmBgYA0KYGBge3J9DQpwcmludChwYXN0ZSgiQW56YWhsIEJ1eWVyczoiLCBsZW5ndGgodW5pcXVlKGNhcmRfYnV5ZXJzJGFjY291bnRfaWQpKSkpDQpwcmludChwYXN0ZSgiQW56YWhsIGdlZnVuZGVuZSBNYXRjaGVzOiIsIGxlbmd0aCh1bmlxdWUoc2ltaWxhcl9ub25fYnV5ZXJzJGFjY291bnRfaWQpKSkpDQpgYGANCg0KDQpXaWUgaGllciBzaWNodGJhciB3aXJkLCBrb25udGUgenUgYWxsZW4gS2FydGVua8OkdWZlciBlaW4gcGFzc2VuZGVyIE5pY2h0LUvDpHVmZXIgZ2VmdW5kZW4gd2VyZGVuLiBBdWYgZGllc2VuIHNvbGxlbiBkaWUgVHJhbnNha3Rpb25zZGF0ZW4gamV0enQgZ2xlaWNoIHdpZSBiZWkgZGVuIEtyZWRpdGthcnRlbmvDpHVmZXJuIGFnZ3JlZ2llcnQgd2VyZGVuLiBBbHMgZXJzdGVzIHdlcmRlbiBkaWUgVHJhbnNha3Rpb25lbiB3aWVkZXIgYXVmIGRhcyBnZWdlYmVuZSBSb2xsdXAtRmVuc3RlciBnZWZpbHRlcnQuDQoNCmBgYHtyfQ0KIyBmaW5kIG1pc3NpbmdzDQpub25fYnV5ZXJfdHJhbnNhY3Rpb25zIDwtIG1lcmdlKHRyYW5zYWN0aW9ucywgc2ltaWxhcl9ub25fYnV5ZXJzWywgYygiYWNjb3VudF9pZCIsICJpc3N1ZWQiKV0sIGJ5PSdhY2NvdW50X2lkJykNCmZpbHRlcmVkX2RmIDwtIGZpbHRlcl90aW1lX3JhbmdlKG5vbl9idXllcl90cmFuc2FjdGlvbnMpDQpgYGANCg0KQWxzIG7DpGNoc3RlcyB3ZXJkZW4gd2llZGVyIEFnZ3JlZ2F0aW9uZW4gZsO8ciBqZWRlbiBNb25hdCBlcnN0ZWxsdCB1bmQgZmVobGVuZGUgTW9uYXRlIGFuYWxvZyB6dSBkZW4gS3JlZGl0a2FydGVua8OkdWZlcm4gaW1wdXRpZXJ0Lg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RmIDwtIGltcHV0ZV9taXNzaW5nX21vbnRocyhmaWx0ZXJlZF9kZikNCg0Kc3VtbWFyeV9kZl9ub25fYnV5ZXJzIDwtIGdldF9zdW1tYXJ5X2RmKGZpbHRlcmVkX2RmKQ0KYGBgDQoNCkF1Y2ggaGllciBoYWJlbiAxNjggS3VuZGVuIGtlaW4gdm9sbHN0w6RuZGlnZXMgUm9sbHVwLUZlbnN0ZXIuIFdpciDDvGJlcnByw7xmZW4gd2llZGVyLCBvYiBmw7xyIGplZGVuIEt1bmRlbiBhbGxlIDEyIE1vbmF0ZSB2b3JoYW5kZW4gc2luZC4NCg0KYGBge3J9DQptb250aF9jb3VudHMgPC0gZ2V0X2luY29tcGxldGVfYnV5ZXJzKHN1bW1hcnlfZGZfbm9uX2J1eWVycykNCnByaW50KHBhc3RlKCJBbnphaGwgS3VuZGVuIG1pdCB3ZW5pZ2VyIGFscyAxMiBNb25hdGVuOiIsIGRpbShtb250aF9jb3VudHMpWzFdKSkNCmBgYA0KDQpEaWUgSW1wdXRhdGlvbiBoYXQgZnVua3Rpb25pZXJ0LCBlcyBnaWJ0IGtlaW5lIEt1bmRlbiBtaXQgd2VuaWdlciBhbHMgMTIgTW9uYXRlbi4gTnVuIHdlcmRlbiBkaWUgRGF0ZW4gd2llZGVyIGF1ZiBlaW5lIFJvdyBhdXNnZXdlaXRldCB1bmQgbWl0IGRlbiBhbmRlcmVuIEluZm9ybWF0aW9uZW4gZGVyIEt1bmRlbiB6dXNhbW1lbmdlZsO8Z3QuDQoNCg0KYGBge3J9DQpzdW1tYXJ5X2RmX25vbl9idXllcnMgPC0gc2V0X21vbnRoX2lkcyhzdW1tYXJ5X2RmX25vbl9idXllcnMpDQpzdW1tYXJ5X2RmX3dpZGUgPC0gcGl2b3RfYnlfbW9udGgoc3VtbWFyeV9kZl9ub25fYnV5ZXJzKQ0Kc3VtbWFyeV9kZl9ub25fYnV5ZXJzIDwtIG1lcmdlKHN1bW1hcnlfZGZfd2lkZSwgc2ltaWxhcl9ub25fYnV5ZXJzLCBieSA9ICJhY2NvdW50X2lkIikNCmBgYA0KDQpadW0gU2NobHVzcyB3ZXJkZW4gZGllIEvDpHVmZXIgdW5kIGRpZSBOaWNodC1Lw6R1ZmVyIGluIGVpbmVtIERhdGVuc2F0eiBrb21iaW5pZXJ0LiBEaWUgQWNjb3VudC1JRCBrYW5uIG5vY2ggZW50ZmVybnQgd2VyZGVuLg0KDQpgYGB7cn0NCmZpbmFsX2RmIDwtIHJiaW5kKHN1bW1hcnlfZGZfYnV5ZXJzLCBzdW1tYXJ5X2RmX25vbl9idXllcnMpDQpmaW5hbF9kZiRhY2NvdW50X2lkIDwtIE5VTEwNCmBgYA0KDQpBdXNzZXJkZW0gc2NoZWluZW4gZWluaWdlIFZhcmlhYmVsbiBhbHMgRmFrdG9yZW4gaW0gRGF0ZW5zYXR6IHp1IHNlaW4sIHdlbGNoZSBlaWdlbnRsaWNoIG51bWVyaXNjaCB3w6RyZW4uDQoNCmBgYHtyfQ0KZmluYWxfZGYkdW5lbXBsb3ltZW50X3JhdGVfOTUgPC0gYXMubnVtZXJpYyhmaW5hbF9kZiR1bmVtcGxveW1lbnRfcmF0ZV85NSkNCmZpbmFsX2RmJHVuZW1wbG95bWVudF9yYXRlXzk1LmFjY291bnRzIDwtIGFzLm51bWVyaWMoZmluYWxfZGYkdW5lbXBsb3ltZW50X3JhdGVfOTUuYWNjb3VudHMpDQpmaW5hbF9kZiRjcmltZXNfOTUgPC0gYXMubnVtZXJpYyhmaW5hbF9kZiRjcmltZXNfOTUpDQpmaW5hbF9kZiRjcmltZXNfOTUuYWNjb3VudHMgPC0gYXMubnVtZXJpYyhmaW5hbF9kZiRjcmltZXNfOTUuYWNjb3VudHMpDQpgYGANCg0KQmVpIGRlbiBiZXJlY2huZXRlbiBTdGFuZGFyZGFid2VpY2h1bmcga29tbXQgZXMgYXVjaCB6dSBOQS1XZXJ0ZW4sIGRhIGVzIE1vbmF0ZSBtaXQgbnVyIGVpbmVyIFRyYW5zYWt0aW9uIGdpYnQgdW5kIGRhcmF1cyBlaW5lIE51bGwtRGl2aXNvbiBlbnRzdGVodC4gRGllc2UgZmVobGVuZGVuIFdlcnRlIHdlcmRlbiBkdXJjaCAwIGVyc2V0enQuDQoNCmBgYHtyfQ0Kc3RkX2NvbHMgPC0gY29sbmFtZXMoZmluYWxfZGYgJT4lIHNlbGVjdChzdGFydHNfd2l0aCgic3RkIikpKQ0KZmluYWxfZGZbLHN0ZF9jb2xzXSA8LSBmaW5hbF9kZlssc3RkX2NvbHNdICU+JSByZXBsYWNlKGlzLm5hKC4pLCAwKQ0KYGBgDQoNClVuc2VyIGZpbmFsZXMgRGF0YWZyYW1lIHNpZWh0IHdpZSBmb2xndCBhdXM6DQoNCmBgYHtyfQ0KZmluYWxfZGYNCmBgYA0KDQpEZXIgRGF0ZW5zYXR6IGVudGjDpGx0IDE0MTIgS3VuZGVuIG1pdCAyMjYgdmVyc2NoaWVkZW5lbiBWYXJpYWJlbG4uIERpZSBIw6RsZnRlIGRlciBLdW5kZW4gc2luZCBLcmVkaXRrYXJ0ZW5rw6R1ZmVyLiBEaWUgWmllbHZhcmlhYmVsIGbDvHIgc3DDpHRlcmUgTW9kZWxsZSBpc3QgImhhc19jYXJkIi4NCg0KIyBFREENCg0KTnVuIHdvbGxlbiB3aXIgdW5zZXIgRGF0YWZyYW1lIG5vY2ggZWluIHdlbmlnIGdlbmF1ZXIgdW50ZXIgZGllIEx1cHBlIG5laG1lbi4gDQoNCiMjIEFuemFobCBLcmVkaXRrYXJ0ZW4tS8OkdWZlciB1bmQgTmljaHQtS8OkdWZlcg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZmluYWxfZGYsIGFlcyh4ID0gaGFzX2NhcmQpKSArDQogIGdlb21fYmFyKGFlcyh5ID0gYWZ0ZXJfc3RhdChjb3VudCkpLCBzdGF0ID0gImNvdW50IiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArDQogIGxhYnMoeCA9ICJJc3QgS8OkdWZlciIsIHkgPSAiQW56YWhsIiwgdGl0bGUgPSAiQW56YWhsIEtyZWRpdGthcnRlbi1Lw6R1ZmVyIHVuZCBOaWNodC1Lw6R1ZmVyIikNCmBgYA0KDQpEYSB3aXIgZsO8ciBqZWRlbiBLcmVkaXRrYXJ0ZW5rw6R1ZmVyIGdlbmF1IGVpbmVuIEvDpHVmZXIgc3VjaGVuLCBpc3QgZGVyIERhdGVuc2F0eiBhdXNnZWdsaWNoZW46IEVzIGhhdCBnbGVpY2hlIHZpZWxlIE5pY2h0LUvDpHVmZXIgd2llIEvDpHVmZXIuDQoNCiMjIFZlcnRlaWx1bmcgZGVyIEthdWZkYXRlbg0KDQpgYGB7cn0NCmdncGxvdChmaW5hbF9kZiwgYWVzKHggPSBpc3N1ZWQsIGZpbGwgPSBmYWN0b3IoaGFzX2NhcmQpKSkgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsNCiAgbGFicyh4ID0gIkthdWZkYXR1bSIsIHkgPSAiRGljaHRlIiwgZmlsbCA9ICJIYXMgQ2FyZCIsIHRpdGxlID0gIlZlcnRlaWx1bmcgZGVyIEthdWZkYXRlbiIpICsNCiAgc2NhbGVfeF9kYXRlKGxpbWl0cyA9IHJhbmdlKGZpbmFsX2RmJGlzc3VlZCkpDQpgYGANCg0KRGllc2VzIERpYWdyYW1tIHplaWd0IGRpZSBWZXJ0ZWlsdW5nIGRlciBLYXVmZGF0ZW4gZGVyIEt1bmRlbi4gRGEgZGFzIEthdWZkYXR1bSBkZXMgTmljaHQtS8OkdWZlcnMgYXVmIGRhcyBkZXMgS8OkdWZlcnMgZ2VzZXR6dCB3aXJkLCBzaW5kIGRpZSBWZXJ0ZWlsdW5nZW4gZGVyIGJlaWRlbiBLbGFzc2VuIGhpZXIgZGVja3VuZ3NnbGVpY2guIEluIGRlbiBBbmZhbmdzamFocmVuIGRlcyBEYXRlbnNhdHplcyBnaWJ0IGVzIG5vY2ggc2VociB3ZW5pZ2UgS3JlZGl0a2FydGVua8OkdWZlLCBkaWUgVGVuZGVueiBpc3QgamVkb2NoIHN0ZWlnZW5kLiBBYiAxOTk2IGVybGViZW4gZGllIEtyZWRpdGthcnRlbmvDpHVmZSBlaW5lbiBzdGVpbGVuIEF1ZnNjaHd1bmcsIHdlbGNoZXIgTWl0dGUgMTk5OCB6dSBzZWluZW0gSMO2aGVwdW5rdCBrb21tdC4gRGFuYWNoIHdlcmRlbiB3aWVkZXIgd2VuaWdlciBLw6R1ZmUgZ2V0w6R0aWd0Lg0KDQojIyBFbnR3aWNrbHVuZyBkZXIgVmVybcO2Z2VuDQoNCkFscyBuw6RjaHN0ZXMgd29sbGVuIHdpciBkYXMgVmVybcO2Z2VuIChiYWxhbmNlKSBkZXIgS3VuZGVuIGFuc2NoYXVlbi4gV2lyIGJlb2JhY2h0ZW4sIG9iIHNpY2ggZGFzIFZlcm3DtmdlbiB2b24gS3VuZGVuIHVuZCBuaWNodCBLdW5kZW4gYmV6w7xnbGljaCBIw7ZoZSB1bmQgRW50d2lja2x1bmcgdW50ZXJzY2hlaWRldC4NCg0KYGBge3J9DQphdmVyYWdlc190cnVlIDwtIGNvbE1lYW5zKGZpbmFsX2RmW2ZpbmFsX2RmJGhhc19jYXJkID09IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoIm1lYW5fYmFsYW5jZV8xIiwgIm1lYW5fYmFsYW5jZV8yIiwgIm1lYW5fYmFsYW5jZV8zIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibWVhbl9iYWxhbmNlXzQiLCAibWVhbl9iYWxhbmNlXzUiLCAibWVhbl9iYWxhbmNlXzYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtZWFuX2JhbGFuY2VfNyIsICJtZWFuX2JhbGFuY2VfOCIsICJtZWFuX2JhbGFuY2VfOSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lYW5fYmFsYW5jZV8xMCIsICJtZWFuX2JhbGFuY2VfMTEiLCAibWVhbl9iYWxhbmNlXzEyIildKQ0KDQphdmVyYWdlc19mYWxzZSA8LSBjb2xNZWFucyhmaW5hbF9kZltmaW5hbF9kZiRoYXNfY2FyZCA9PSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoIm1lYW5fYmFsYW5jZV8xIiwgIm1lYW5fYmFsYW5jZV8yIiwgIm1lYW5fYmFsYW5jZV8zIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lYW5fYmFsYW5jZV80IiwgIm1lYW5fYmFsYW5jZV81IiwgIm1lYW5fYmFsYW5jZV82IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lYW5fYmFsYW5jZV83IiwgIm1lYW5fYmFsYW5jZV84IiwgIm1lYW5fYmFsYW5jZV85IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lYW5fYmFsYW5jZV8xMCIsICJtZWFuX2JhbGFuY2VfMTEiLCAibWVhbl9iYWxhbmNlXzEyIildKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fbGluZShhZXMoeCA9IDE6MTIsIHkgPSBhdmVyYWdlc190cnVlLCBjb2xvciA9ICJLw6R1ZmVyIikpICsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gMToxMiwgeSA9IGF2ZXJhZ2VzX2ZhbHNlLCBjb2xvciA9ICJOaWNodC1Lw6R1ZmVyIikpICsNCiAgbGFicyh4ID0gIk1vbmF0IiwgeSA9ICJEdXJjaHNjaG5pdHRsaWNoZXMgVmVybcO2Z2VuIiwgdGl0bGUgPSAiVmVybcO2Z2Vuc2VudHdpY2tsdW5nIikgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZSA9ICJLdW5kZW4iLCB2YWx1ZXMgPSBjKCJLw6R1ZmVyIiA9ICJzdGVlbGJsdWUiLCAiTmljaHQtS8OkdWZlciIgPSAicmVkIikpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMSwgMTIpKSArDQogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIE5BKSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQ0KYGBgDQoNCktyZWRpdGthcnRlbmvDpHVmZXIgaGFiZW4gaW0gU2Nobml0dCBlaW4gaMO2aGVyZXMgVmVybcO2Z2VuIGFscyBOaWNodC1Lw6R1ZmVyLiBEYXMgVmVybcO2Z2VuIGFsbGVyIEt1bmRlbiBzdGVpZ3QgdGVuZGVuemllbGwsIHdvYmVpIGRhcyBkZXIgS8OkdWZlciBzdGVpbGVyIGFscyBkYXMgZGVyIE5pY2h0LUvDpHVmZXIgc3RlaWd0LiBBYiBkZW0gc2llYnRlbiBNb25hdCBzY2hlaW50IGFiZXIgYXVjaCBkaWVzZSBWZXJtw7ZnZW5zc3RlaWdlcnVuZyBhYnp1ZmxhY2hlbi4NCg0KIyMgQW56YWhsIFRyYW5zYWt0aW9uZW4NCg0KQWxzIG7DpGNoc3RlcyB3b2xsZW4gd2lyIHVudGVyc3VjaGVuLCBvYiBzaWNoIEtyZWRpdGthcnRlbmvDpHVmZXIgdW5kIE5pY2h0LUvDpHVmZXIgZHVyY2ggZGllIEFuemFobCBnZXTDpHRpZ3RlciBUcmFuc2FrdGlvbmVuIHVudGVyc2NoZWlkZW4uIERhZsO8ciBzY2hhdWVuIHdpciBkaWUgZHVyY2hzY2huaXR0bGljaGUgQW56YWhsIFRyYW5zYWt0aW9uZW4gZsO8ciBiZWlkZSBLbGFzc2VuIGFuLg0KDQpgYGB7cn0NCmF2Z19zdW1fbmVnYXRpdmVfdHJ1ZSA8LSBtZWFuKHJvd1N1bXMoZmluYWxfZGZbZmluYWxfZGYkaGFzX2NhcmQgPT0gVFJVRSwgZ3JlcCgiY291bnRfbmVnYXRpdmVfZGlmZmVyZW5jZSIsIGNvbG5hbWVzKGZpbmFsX2RmKSldKSkNCmF2Z19zdW1fbmVnYXRpdmVfZmFsc2UgPC0gbWVhbihyb3dTdW1zKGZpbmFsX2RmW2ZpbmFsX2RmJGhhc19jYXJkID09IEZBTFNFLCBncmVwKCJjb3VudF9uZWdhdGl2ZV9kaWZmZXJlbmNlIiwgY29sbmFtZXMoZmluYWxfZGYpKV0pKQ0KDQphdmdfc3VtX3Bvc2l0aXZlX3RydWUgPC0gbWVhbihyb3dTdW1zKGZpbmFsX2RmW2ZpbmFsX2RmJGhhc19jYXJkID09IFRSVUUsIGdyZXAoImNvdW50X3Bvc2l0aXZlX2RpZmZlcmVuY2UiLCBjb2xuYW1lcyhmaW5hbF9kZikpXSkpDQphdmdfc3VtX3Bvc2l0aXZlX2ZhbHNlIDwtIG1lYW4ocm93U3VtcyhmaW5hbF9kZltmaW5hbF9kZiRoYXNfY2FyZCA9PSBGQUxTRSwgZ3JlcCgiY291bnRfcG9zaXRpdmVfZGlmZmVyZW5jZSIsIGNvbG5hbWVzKGZpbmFsX2RmKSldKSkNCg0KYXZnX3N1bV9ib3RoX3RydWUgPSBhdmdfc3VtX25lZ2F0aXZlX3RydWUgKyBhdmdfc3VtX3Bvc2l0aXZlX3RydWUNCmF2Z19zdW1fYm90aF9mYWxzZSA9IGF2Z19zdW1fbmVnYXRpdmVfZmFsc2UgKyBhdmdfc3VtX3Bvc2l0aXZlX2ZhbHNlDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9iYXIoYWVzKHggPSBjKCJLw6R1ZmVyIiwgIk5pY2h0LUvDpHVmZXIiKSwgeSA9IGMoYXZnX3N1bV9ib3RoX3RydWUsIGF2Z19zdW1fYm90aF9mYWxzZSkpLCBzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9IGMoInN0ZWVsYmx1ZSIsICJyZWQiKSkgKw0KICBsYWJzKHggPSAiIiwgeSA9ICJEdXJjaHNjaG5pdHRsaWNoZSBBbnphaGwgVHJhbnNha3Rpb25lbiIsIHRpdGxlID0gIkR1cmNoc2Nobml0dGxpY2hlIEFuemFobCBUcmFuc2FrdGlvbmVuIGltIGdhbnplbiBSb2xsdXAtRmVuc3RlciIpDQpgYGANCg0KS3JlZGl0a2FydGVua8OkdWZlciBoYWJlbiBhbHNvIGltIFNjaG5pdHQgbWVociBUcmFuc2FrdGlvbmVuIGdldMOkdGlndCBhbHMgTmljaHQtS8OkdWZlciBpbSBSb2xsdXAtRmVuc3Rlci4gRG9jaCB3aWUgVmVyaMOkbHQgZXMgc2ljaCwgd2VubiB3aXIgQW56YWhsIHBvc2l0aXZlIHVuZCBBbnphaGwgbmVnYXRpdmUgVHJhbnNha3Rpb25lbiBzZXBhcmF0IGJldHJhY2h0ZW4/DQoNCmBgYHtyfQ0KZ2dwbG90KCkgKw0KICBnZW9tX2JhcihhZXMoeCA9IGMoIkvDpHVmZXIgQXVzZ2FiZW4iLCAiTmljaHQtS8OkdWZlciBBdXNnYWJlbiIsICJLw6R1ZmVyIEVpbm5haG1lbiIsICJOaWNodC1Lw6R1ZmVyIEVpbm5haG1lbiIpLCB5ID0gYyhhdmdfc3VtX25lZ2F0aXZlX3RydWUsIGF2Z19zdW1fbmVnYXRpdmVfZmFsc2UsIGF2Z19zdW1fcG9zaXRpdmVfdHJ1ZSwgYXZnX3N1bV9wb3NpdGl2ZV9mYWxzZSkpLCBzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9IGMoInN0ZWVsYmx1ZSIsICJyZWQiLCAic3RlZWxibHVlIiwgInJlZCIpKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIkR1cmNoc2Nobml0dGxpY2hlIEFuemFobCBUcmFuc2FrdGlvbmVuIiwgdGl0bGUgPSAiRHVyY2hzY2huaXR0bGljaGUgQW56YWhsIFRyYW5zYWt0aW9uZW4gaW0gZ2FuemVuIFJvbGx1cC1GZW5zdGVyIikNCmBgYA0KDQpCZWlkZSBLbGFzc2VuIGhhYmVuIG1laHIgcG9zaXRpdmUgVHJhbnNha3Rpb25lbiBhbHMgbmVnYXRpdmUsIHdvYmVpIEvDpHVmZXIgYmVpIHBvc2l0aXZlbiBzb3dpZSBiZWkgbmVnYXRpdmVuIGR1cmNoc2Nobml0dGxpY2ggbWVociBoYWJlbi4gQmVpIGRlbiBFaW5uYWhtZW4gaXN0IGRlciBVbnRlcnNjaGllZCBlaW4gd2VuaWcgaMO2aGVyIGFscyBiZWkgZGVuIEF1c2dhYmVuLg0KDQojIyBEYXJsZWhlbg0KDQpIaWVyIHNvbGwgdW50ZXJzdWNodCB3ZXJkZW4sIG9iIEvDpHVmZXIgbWVociBEYXJsZWhlbiBiZWkgZGVyIEJhbmsgYXVmbmVobWVuIGFscyBOaWNodC1Lw6R1ZmVyLg0KDQpgYGB7cn0NCmZpbmFsX2RmDQpgYGANCg0KDQpgYGB7cn0NCmNhcmRfYnV5ZXJzX3dpdGhfbG9hbiA8LSBmaW5hbF9kZltmaW5hbF9kZiRoYXNfY2FyZCAmIGZpbmFsX2RmJHN0YXR1cyAhPSAibm9fbG9hbiIsXQ0Kbm9uX2J1eWVyc193aXRoX2xvYW4gPC0gZmluYWxfZGZbIWZpbmFsX2RmJGhhc19jYXJkICYgZmluYWxfZGYkc3RhdHVzICE9ICJub19sb2FuIixdDQoNCmNhcmRfYnV5ZXJzX3dpdGhfbG9hbl9jb3VudCA8LSBsZW5ndGgoY2FyZF9idXllcnNfd2l0aF9sb2FuJGhhc19jYXJkKQ0Kbm9uX2J1eWVyc193aXRoX2xvYW5fY291bnQgPC0gbGVuZ3RoKG5vbl9idXllcnNfd2l0aF9sb2FuJGhhc19jYXJkKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fYmFyKGFlcyh4ID0gYygiS8OkdWZlciIsICJOaWNodC1Lw6R1ZmVyIiksIHkgPSBjKGNhcmRfYnV5ZXJzX3dpdGhfbG9hbl9jb3VudCwgbm9uX2J1eWVyc193aXRoX2xvYW5fY291bnQpKSwgc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSBjKCJzdGVlbGJsdWUiLCAicmVkIikpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiQW56YWhsIiwgdGl0bGUgPSAiQW56YWhsIEt1bmRlbiBtaXQgRGFybGVoZW4iKQ0KYGBgDQoNCkZhc3QgZG9wcGVsdCBzbyB2aWVsZSBLw6R1ZmVyIHdpZSBOaWNodC1Lw6R1ZmVyIGhhYmVuIGVpbiBEYXJsZWhlbiBhdWZnZW5vbW1lbi4NCg0KIyBNb2RlbGxlDQoNCkFscyBuw6RjaHN0ZXMgd29sbGVuIHdpciBNb2RlbGxlIHRyYWluaWVyZW4sIHdlbGNoZSBBbmhhbmQgZGVyIE1lcmttYWxlIGRlciBLdW5kZW4gdm9yaGVyc2FnZW4gc29sbGVuLCBvYiBlaW4gS3VuZGUgZWluIEvDpHVmZXIgaXN0IG9kZXIgbmljaHQuIERpZXNlcyBNb2RlbGwgc29sbCBkYW5uIGdlYnJhdWNodCB3ZXJkZW4sIHVtIHp1IGVya2VubmVuLCB3ZWxjaGUgYmVzdGVoZW5kZW4gS3VuZGVuIG9obmUgS3JlZGl0a2FydGUgYW0gZWhlc3RlbiBlaW5lIGthdWZlbiB3w7xyZGVuLiANCg0KIyMgRnVua3Rpb25lbiB6dXIgRXZhbHVpZXJ1bmcgdm9uIE1vZGVsbGVuDQoNCkRhbWl0IGRpZSBFdmFsdWllcnVuZyB2b24gTW9kZWxsZW4gbmljaHQgcmVkdW5kYW50ZW4gQ29kZSBlbnRow6RsdCwgd2lyZCBoaWVyIHZlcnN1Y2h0LCBGdW5rdGlvbmVuIHp1IGVyc3RlbGxlbiwgd2VsY2hlIGJlaSB2ZXJzY2hpZWRlbmVuIE1vZGVsbGVuIGdlYnJhdWNodCB3ZXJkZW4ga8O2bm5lbi4NCg0KIyMjIFRyYWluLVRlc3QtU3BsaXQNCg0KQWxzIFZvcmJlcmVpdHVuZyBmw7xyIGRpZSBNb2RlbGxlIG3DvHNzZW4gd2lyIHVuc2VyZSBEYXRlbiB6dSBUcmFpbmluZ3MtIHVuZCBUZXN0ZGF0ZW4gdW50ZXJ0ZWlsZW4uIERhZsO8ciBlcnN0ZWxsZW4gd2lyIGVpbmUgRnVua3Rpb24sIHdlbGNoZSBhdWYgdmVyc2NoaWVkZW5lbiBWYXJpYW50ZW4gZGVzIERhdGVuc2F0eiBnZWJyYXVjaHQgd2VyZGVuIGthbm4uDQoNCg0KYGBge3J9DQpzcGxpdF9kYXRhIDwtIGZ1bmN0aW9uKGRmLCB0ZXN0X3NpemUgPSAwLjIpIHsNCiAgc2V0LnNlZWQoMjcpDQogIGRmIDwtIGRmW29yZGVyKGRmJGlzc3VlZCwgZGVjcmVhc2luZyA9IEZBTFNFKSwgXQ0KICANCiAgIyBHZXQgbnVtYmVyIG9mIHJvd3MgaW4gdGhlIGRhdGFmcmFtZQ0KICBuX3Jvd3MgPC0gbnJvdyhkZikNCg0KIyBDYWxjdWxhdGUgdGhlIG51bWJlciBvZiByb3dzIGluIHRoZSB0ZXN0IHNldCAoMjAlIG9mIHRoZSB0b3RhbCBudW1iZXIgb2Ygcm93cykNCm5fdGVzdF9yb3dzIDwtIGZsb29yKG5fcm93cyAqIDAuMikNCg0KZGYkaXNzdWVkIDwtIE5VTEwNCg0KIyBDcmVhdGUgdGhlIHRyYWluIGFuZCB0ZXN0IHNldHMNCnRyYWluIDwtIGhlYWQoZGYsIG5fcm93cyAtIG5fdGVzdF9yb3dzKQ0KdGVzdCA8LSB0YWlsKGRmLCBuX3Rlc3Rfcm93cykNCiAgDQoNCiNzcGxpdCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRmJGhhc19jYXJkLCBwID0gMSAtIHRlc3Rfc2l6ZSwgbGlzdCA9IEZBTFNFKQ0KICMgdHJhaW4gPC0gZGZbc3BsaXQsIF0NCiAgI3Rlc3QgPC0gZGZbLXNwbGl0LCBdDQoNCiMgY3JlYXRlIGN2IGZvbGRzDQogIHRyYWluX3NldHMgPC0gYygpDQogIHZhbF9zZXRzIDwtIGMoKQ0KDQogIHNldC5zZWVkKDI3KQ0KICBjdl9mb2xkcyA8LSB2Zm9sZF9jdih0cmFpbiwgdiA9IDEwKQ0KICAjIEl0ZXJpZXJlIMO8YmVyIGFsbGUgRm9sZHMNCiAgZm9yIChpIGluIDE6bnJvdyhjdl9mb2xkcykpIA0KICB7DQogICAgIyBXw6RobGUgZGVuIGFrdHVlbGxlbiBUcmFpbmluZ3MtIHVuZCBWYWxpZGllcnVuZ3NkYXRlbnNhdHogYXVzDQogICAgdHJhaW5fc2V0IDwtIGN2X2ZvbGRzJHNwbGl0c1tbaV1dICU+JSBhbmFseXNpcygpDQogICAgdmFsX3NldCA8LSBjdl9mb2xkcyRzcGxpdHNbW2ldXSAlPiUgYXNzZXNzbWVudCgpDQogIA0KICAgICMgU3BlaWNoZXJlIGRlbiBha3R1ZWxsZW4gVHJhaW5pbmdzLSB1bmQgVmFsaWRpZXJ1bmdzZGF0ZW5zYXR6IGluIGRlbiBMaXN0ZW4NCiAgICB0cmFpbl9zZXRzW1tpXV0gPC0gdHJhaW5fc2V0DQogICAgdmFsX3NldHNbW2ldXSA8LSB2YWxfc2V0DQogIH0NCiAgDQogIHJldHVybihsaXN0KHRyYWluID0gdHJhaW4sIHRlc3QgPSB0ZXN0LCBjdl9mb2xkcyA9IGN2X2ZvbGRzLCB0cmFpbl9zZXRzID0gdHJhaW5fc2V0cywgdmFsX3NldHMgPSB2YWxfc2V0cykpDQp9DQpgYGANCg0KIyMjIE1ldHJpa2VuDQoNCkRhbWl0IHdpciBkaWUgdmVyc2NoaWVkZW5lbiBNb2RlbGxlIGJlc3NlciBldmFsdWllcmVuIGvDtm5uZW4sIG3DvHNzZW4gd2lyIGRpZSBnbGVpY2hlbiBLZW5uemFobGVuIHVuZCBBdXN3ZXJ0dW5nZW4gcHJvIE1vZGVsbCBtYWNoZW4uIFp1IGRpZXNlbSBad2VjayBkZWZpbmllcmVuIHdpciBlaW5pZ2UgRnVua3Rpb25lbiwgZGFtaXQgd2lyIHdlbmlnZXIgcmVkdW5kYW50ZW4gQ29kZSBoYWJlbiB1bmQgdW5zZXJlIEVyZ2Vibmlzc2UgaW4gZWluZW0gZWluaGVpdGxpY2hlLCB2ZXJnbGVpY2hiYXJlbiBGb3JtYXQgZGFoZXJrb21tZW4uIA0KDQpEYWbDvHIgZXJzdGVsbGVuIHdpciBlaW5lIEZ1bmt0aW9uLCBkaWUgZGllIEdlbmF1aWdrZWl0IChBY2N1cmFjeSksIENvaGVuJ3MgS2FwcGEsIE1hdHRoZXdzIEtvcnJlbGF0aW9uLCBQcsOkemlzaW9uIChQcmVjaXNpb24pLCBFcmlubmVydW5nIChSZWNhbGwpIHVuZCBkZW4gRjEtU2NvcmUgZsO8ciBlaW5lIFJlaWhlIHZvbiBWb3JoZXJzYWdlbiB1bmQgZGVyZW4gZW50c3ByZWNoZW5kZW4gd2FocmVuIFdlcnRlIGJlcmVjaG5ldC4NCg0KV2FzIGJlZGV1dGVuIGRpZXNlIEtlbm56YWhsZW4gZ2VuYXU/DQoNCkFjY3VyYWN5IChHZW5hdWlna2VpdCk6IERpZSBBY2N1cmFjeSBpc3QgZGVyIFByb3plbnRzYXR6IGRlciBWb3JoZXJzYWdlbiwgZGllIG1pdCBkZW4gdGF0c8OkY2hsaWNoZW4gV2VydGVuIMO8YmVyZWluc3RpbW1lbi4gU2llIHdpcmQgYmVyZWNobmV0IGFscyBBbnphaGwgZGVyIGtvcnJla3RlbiBWb3JoZXJzYWdlbiBnZXRlaWx0IGR1cmNoIGRpZSBHZXNhbXR6YWhsIGRlciBWb3JoZXJzYWdlbi4NCg0KQ29oZW4ncyBLYXBwYSAoQ29oZW4ncyBLYXBwYSk6IENvaGVuJ3MgS2FwcGEgaXN0IGVpbmUgTWVzc2dyw7bDn2UgZsO8ciBkaWUgUXVhbGl0w6R0IHZvbiBiaW7DpHJlbiBLbGFzc2lmaWthdGlvbmVuLiBFcyB3aXJkIHZlcndlbmRldCwgdW0gZGllIMOcYmVyZWluc3RpbW11bmcgendpc2NoZW4gendlaSBLbGFzc2lmaWthdG9yZW4genUgbWVzc2VuLCBpbmRlbSBlcyBkaWUgw5xiZXJlaW5zdGltbXVuZyDDvGJlciBkZXIgZXJ3YXJ0ZXRlbiDDnGJlcmVpbnN0aW1tdW5nIGR1cmNoIFp1ZmFsbCBiZXJlY2huZXQuIEVpbiBLYXBwYS1XZXJ0IHZvbiAxIGJlZGV1dGV0IHBlcmZla3RlIMOcYmVyZWluc3RpbW11bmcsIGVpbiBXZXJ0IHZvbiAwIGJlZGV1dGV0IGtlaW5lIMOcYmVyZWluc3RpbW11bmcsIGRpZSBiZXNzZXIgaXN0IGFscyBadWZhbGwsIHVuZCBlaW4gV2VydCB2b24gLTEgYmVkZXV0ZXQga29tcGxldHQgZmFsc2NoZSBLbGFzc2lmaWthdGlvbmVuLg0KDQpNYXR0aGV3cyBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCAoTWF0dGhld3MgS29ycmVsYXRpb24pOiBEZXIgTWF0dGhld3MgS29ycmVsYXRpb24gQ29lZmZpY2llbnQgKE1DQykgaXN0IGVpbmUgTWVzc2dyw7bDn2UgZsO8ciBkaWUgUXVhbGl0w6R0IHZvbiBiaW7DpHJlbiBLbGFzc2lmaWthdGlvbmVuLiBFciByZWljaHQgdm9uIC0xIGJpcyAxLCB3b2JlaSBlaW4gV2VydCB2b24gMSBwZXJmZWt0ZSBLbGFzc2lmaWthdGlvbiBiZWRldXRldCwgZWluIFdlcnQgdm9uIDAgZWluZSBLbGFzc2lmaWthdGlvbiwgZGllIG5pY2h0IGJlc3NlciBhbHMgWnVmYWxsIGlzdCwgdW5kIGVpbiBXZXJ0IHZvbiAtMSBlaW5lIGtvbXBsZXR0IGZhbHNjaGUgS2xhc3NpZmlrYXRpb24gYmVkZXV0ZXQuDQoNClByZWNpc2lvbiAoUHLDpHppc2lvbik6IERpZSBQcsOkemlzaW9uIGlzdCBkZXIgUHJvemVudHNhdHogZGVyIFZvcmhlcnNhZ2VuLCBkaWUgdGF0c8OkY2hsaWNoIGtvcnJla3Qgd2FyZW4sIHVudGVyIGRlciBBbm5haG1lLCBkYXNzIGFsbGUgVm9yaGVyc2FnZW4ga29ycmVrdCBzaW5kLiBTaWUgd2lyZCBiZXJlY2huZXQgYWxzIEFuemFobCBkZXIga29ycmVrdGVuIFZvcmhlcnNhZ2VuIGbDvHIgZGllIHBvc2l0aXZlIEtsYXNzZSBnZXRlaWx0IGR1cmNoIGRpZSBHZXNhbXR6YWhsIGRlciBWb3JoZXJzYWdlbiBmw7xyIGRpZSBwb3NpdGl2ZSBLbGFzc2UuDQoNClJlY2FsbCAoRXJpbm5lcnVuZyk6IERlciBSZWNhbGwgaXN0IGRlciBQcm96ZW50c2F0eiBkZXIgdGF0c8OkY2hsaWNoIHBvc2l0aXZlbiBXZXJ0ZSwgZGllIGtvcnJla3Qgdm9yaGVyZ2VzYWd0IHd1cmRlbi4gRXIgd2lyZCBiZXJlY2huZXQgYWxzIEFuemFobCBkZXIga29ycmVrdGVuIFZvcmhlcnNhZ2VuIGbDvHIgZGllIHBvc2l0aXZlIEtsYXNzZSBnZXRlaWx0IGR1cmNoIGRpZSBHZXNhbXR6YWhsIGRlciB0YXRzw6RjaGxpY2ggcG9zaXRpdmVuIFdlcnRlLg0KDQpGMSBzY29yZSAoRjEtV2VydCk6IERlciBGMS1XZXJ0IGlzdCBlaW4gTWHDnyBmw7xyIGRpZSBRdWFsaXTDpHQgdm9uIGJpbsOkcmVuIEtsYXNzaWZpa2F0aW9uZW4sIGRhcyBkaWUgSGFybW9uaWVzY2hlIE1pc2NodW5nIHZvbiBQcsOkemlzaW9uIHVuZCBSZWNhbGwgZGFyc3RlbGx0LiBFcyB3aXJkIGJlcmVjaG5ldCBhbHMgZGVyIEhhcm1vbmllc2NoZSBNaXR0ZWx3ZXJ0IHZvbiBQcsOkemlzaW9uIHVuZCBSZWNhbGwuIEVpbiBob2hlciBGMS1XZXJ0IGJlZGV1dGV0LCBkYXNzIHNvd29obCBQcsOkemlzaW9uIGFscyBhdWNoIFJlY2FsbCBob2NoIHNpbmQuDQoNCmBgYHtyfQ0KZ2V0X21ldHJpY3MgPC0gZnVuY3Rpb24ocHJlZGljdGlvbnMsIHRydWVfdmFsdWVzLCBtb2RlbF9uYW1lKSB7DQogICMgQ2FsY3VsYXRlIGFjY3VyYWN5DQogIGFjY3VyYWN5IDwtIHN1bShwcmVkaWN0aW9ucyA9PSB0cnVlX3ZhbHVlcykgLyBsZW5ndGgocHJlZGljdGlvbnMpDQogIA0KICAjIENhbGN1bGF0ZSBDb2hlbidzIGthcHBhDQogIG4gPC0gbGVuZ3RoKHByZWRpY3Rpb25zKQ0KICBvYnNlcnZlZF9hZ3JlZW1lbnQgPC0gc3VtKHByZWRpY3Rpb25zID09IHRydWVfdmFsdWVzKQ0KICBleHBlY3RlZF9hZ3JlZW1lbnQgPC0gc3VtKHByZWRpY3Rpb25zID09IHRydWVfdmFsdWVzKSAvIG4NCiAga2FwcGEgPC0gKG9ic2VydmVkX2FncmVlbWVudCAtIGV4cGVjdGVkX2FncmVlbWVudCkgLyAobiAtIGV4cGVjdGVkX2FncmVlbWVudCkNCiAgDQogICMgQ2FsY3VsYXRlIE1hdHRoZXdzIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50DQogIGNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUocHJlZGljdGlvbnMsIHRydWVfdmFsdWVzKQ0KICB0cCA8LSBjb25mdXNpb25fbWF0cml4WzIsMl0NCiAgdG4gPC0gY29uZnVzaW9uX21hdHJpeFsxLDFdDQogIGZwIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMiwxXQ0KICBmbiA8LSBjb25mdXNpb25fbWF0cml4WzEsMl0NCiAgbWF0dGhld3MgPC0gKHRwICogdG4gLSBmcCAqIGZuKSAvIHNxcnQoKHRwICsgZnApICogKHRwICsgZm4pICogKHRuICsgZnApICogKHRuICsgZm4pKQ0KICANCiAgIyBDYWxjdWxhdGUgcHJlY2lzaW9uIGFuZCByZWNhbGwNCiAgcHJlY2lzaW9uIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMiwyXSAvIHN1bShjb25mdXNpb25fbWF0cml4WzIsXSkNCiAgcmVjYWxsIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMiwyXSAvIHN1bShjb25mdXNpb25fbWF0cml4WywyXSkNCiAgDQogICMgQ2FsY3VsYXRlIEYxIHNjb3JlDQogIGYxIDwtIDIgKiAocHJlY2lzaW9uICogcmVjYWxsKSAvIChwcmVjaXNpb24gKyByZWNhbGwpDQogIA0KICAjIENyZWF0ZSBhIGRhdGEgZnJhbWUgb2YgdGhlIG1ldHJpY3MNCiAgbWV0cmljcyA8LSBkYXRhLmZyYW1lKG1vZGVsID0gbW9kZWxfbmFtZSwgYWNjdXJhY3kgPSBhY2N1cmFjeSwga2FwcGEgPSBrYXBwYSwgbWF0dGhld3MgPSBtYXR0aGV3cywNCiAgICAgICAgICAgICAgICAgICAgICAgIHByZWNpc2lvbiA9IHByZWNpc2lvbiwgcmVjYWxsID0gcmVjYWxsLCBmMSA9IGYxKQ0KICANCiAgIyBSZXR1cm4gdGhlIGRhdGEgZnJhbWUNCiAgcmV0dXJuKG1ldHJpY3MpDQp9DQpgYGANCg0KIyMjIEtvbmZ1c2lvbnNtYXRyaXggbWl0IFBsb3QNCg0KRGllc2UgRnVua3Rpb24gZXJzdGVsbHQgZWluZSBLb25mdXNpb25zbWF0cml4IHVuZCBnaWJ0IHNpZSBhbHMgUGxvdCB6dXLDvGNrLiANCg0KRWluZSBLb25mdXNpb25zbWF0cml4IGlzdCBlaW4gd2ljaHRpZ2VzIFdlcmt6ZXVnIHp1ciBFdmFsdWF0aW9uIHZvbiBLbGFzc2lmaWthdGlvbnNtb2RlbGxlbi4gU2llIHplaWd0IGFuLCB3aWUgZ3V0IGRhcyBNb2RlbGwgaW4gZGVyIExhZ2UgaXN0LCBkaWUgdmVyc2NoaWVkZW5lbiBLbGFzc2VuIHJpY2h0aWcgenUgaWRlbnRpZml6aWVyZW4uIEluIGVpbmVyIEtvbmZ1c2lvbnNtYXRyaXggd2VyZGVuIGRpZSB0YXRzw6RjaGxpY2hlbiB1bmQgZGllIHZvbiBkZW0gTW9kZWxsIHZvcmhlcmdlc2FndGVuIEtsYXNzZW4gZ2VnZW7DvGJlcmdlc3RlbGx0LiBEaWUgTWF0cml4IGlzdCBpbiB2aWVyIFF1YWRyYW50ZW4gdW50ZXJ0ZWlsdDogdHJ1ZSBwb3NpdGl2ZXMgKFRQKSwgdHJ1ZSBuZWdhdGl2ZXMgKFROKSwgZmFsc2UgcG9zaXRpdmVzIChGUCkgdW5kIGZhbHNlIG5lZ2F0aXZlcyAoRk4pLiBUUCBzaW5kIGRpZSBGw6RsbGUsIGluIGRlbmVuIGRhcyBNb2RlbGwgZGllIEtsYXNzZSByaWNodGlnIHZvcmhlcmdlc2FndCBoYXQsIFROIHNpbmQgZGllIEbDpGxsZSwgaW4gZGVuZW4gZGFzIE1vZGVsbCBkaWUgS2xhc3NlIHJpY2h0aWcgdm9yaGVyZ2VzYWd0IGhhdCB1bmQgZGllc2UgS2xhc3NlIGF1Y2ggdGF0c8OkY2hsaWNoIHZvcmxpZWd0LCBGUCBzaW5kIGRpZSBGw6RsbGUsIGluIGRlbmVuIGRhcyBNb2RlbGwgZWluZSBLbGFzc2Ugdm9yaGVyZ2VzYWd0IGhhdCwgZGllIGluIFdpcmtsaWNoa2VpdCBuaWNodCB2b3JsaWVndCwgdW5kIEZOIHNpbmQgZGllIEbDpGxsZSwgaW4gZGVuZW4gZGFzIE1vZGVsbCBlaW5lIEtsYXNzZSBuaWNodCB2b3JoZXJnZXNhZ3QgaGF0LCBkaWUgaW4gV2lya2xpY2hrZWl0IHZvcmxpZWd0LiBFaW5lIEtvbmZ1c2lvbnNtYXRyaXggaXN0IGhpbGZyZWljaCwgdW0gZGllIEdlbmF1aWdrZWl0LCBTZW5zaXRpdml0w6R0IHVuZCBTcGV6aWZpdMOkdCBkZXMgTW9kZWxscyB6dSBiZXJlY2huZW4gdW5kIHVtIHp1IHNlaGVuLCBhbiB3ZWxjaGVuIFN0ZWxsZW4gZGFzIE1vZGVsbCBTY2h3w6RjaGVuIGhhdC4gU2llIGthbm4gYXVjaCB2ZXJ3ZW5kZXQgd2VyZGVuLCB1bSBkaWUgTGVpc3R1bmcgdm9uIHZlcnNjaGllZGVuZW4gTW9kZWxsZW4gbWl0ZWluYW5kZXIgenUgdmVyZ2xlaWNoZW4uDQoNCmBgYHtyfQ0KcGxvdF9jb25mdXNpb25fbWF0cml4IDwtIGZ1bmN0aW9uKHByZWRpY3Rpb25zLCB0cnVlX3ZhbHVlcykgew0KICAjIEVyc3RlbGxlIGVpbmUgQ29uZnVzaW9uIE1hdHJpeCBhbHMgRGF0YSBGcmFtZQ0KICBjb25mdXNpb25fbWF0cml4X2RmIDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbnMsIHRydWVfdmFsdWVzKQ0KICANCiAgIyBaw6RobGUgZGllIEjDpHVmaWdrZWl0ZW4gamVkZXIgS29tYmluYXRpb24gdm9uIFZvcmhlcnNhZ2UtIHVuZCBUcnVlLVdlcnRlbg0KICBjb3VudHNfZGYgPC0gY291bnQoY29uZnVzaW9uX21hdHJpeF9kZiwgcHJlZGljdGlvbnMsIHRydWVfdmFsdWVzKQ0KICANCiAgIyBFcnN0ZWxsZSBlaW5lbiBnZ3Bsb3QtUGxvdA0KICBnZ3Bsb3QoZGF0YSA9IGNvdW50c19kZiwgYWVzKHggPSBwcmVkaWN0aW9ucywgeSA9IHRydWVfdmFsdWVzKSkgKw0KICAgIGdlb21fdGlsZShhZXMoZmlsbCA9IG4pKSArDQogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4pKSArDQogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gImRhcmtncmVlbiIpICsNCiAgICBsYWJzKHggPSAiUHJlZGljdGVkIENsYXNzIiwgeSA9ICJUcnVlIENsYXNzIiwgdGl0bGUgPSAiQ29uZnVzaW9uIE1hdHJpeCIpDQp9DQpgYGANCg0KIyMjIFJPQyAvIEFVQw0KDQpEaWVzZSBGdW5rdGlvbiB6ZWljaG5ldCBkaWUgUk9DLUt1cnZlIHVuZCBiZXJlY2huZXQgZGllIEFyZWEgdW5kIEN1cnZlLiANCg0KRGllIFJPQy1LdXJ2ZSAoUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIGN1cnZlKSBpc3QgZWluIHdpY2h0aWdlcyBXZXJremV1ZyB6dXIgQmV3ZXJ0dW5nIHZvbiBLbGFzc2lmaWthdG9yZW4uIFNpZSB6ZWlndCBkaWUgTGVpc3R1bmcgZGVzIEtsYXNzaWZpa2F0b3JzIGJlaSB2ZXJzY2hpZWRlbmVuIFNjaHdlbGxlbndlcnRlbiBhbiwgZGllIHp1ciBVbnRlcnNjaGVpZHVuZyB6d2lzY2hlbiB6d2VpIEtsYXNzZW4gdmVyd2VuZGV0IHdlcmRlbi4gRGllIFJPQy1LdXJ2ZSBpc3QgYmVzb25kZXJzIG7DvHR6bGljaCwgd2VubiBkaWUgYmVpZGVuIEtsYXNzZW4gaW0gVmVyaMOkbHRuaXMgdW5hdXNnZWdsaWNoZW4gc2luZCwgd2llIGVzIG9mdCBkZXIgRmFsbCBpc3QsIHdlbm4gZXMgZGFydW0gZ2VodCwgc2VsdGVuZSBFcmVpZ25pc3NlIHdpZSBLcmFua2hlaXRlbiBvZGVyIEJldHJ1ZyB6dSBlcmtlbm5lbi4NCg0KRGllIFJPQy1LdXJ2ZSBpc3QgYXVmIGRlciB4LUFjaHNlIGRlciBmYWxzY2gtcG9zaXRpdi1SYXRlIChGUFIpIHVuZCBhdWYgZGVyIHktQWNoc2UgZGVyIHdhaHItcG9zaXRpdi1SYXRlIChUUFIpIGF1ZmdldHJhZ2VuLiBEZXIgRlBSIGdpYnQgYW4sIHdpZSB2aWVsZSBmYWxzY2ggcG9zaXRpdmUgRXJnZWJuaXNzZSBlcyBnaWJ0LCB3w6RocmVuZCBkZXIgVFBSIGFuZ2lidCwgd2llIHZpZWxlIHdhaHIgcG9zaXRpdmUgRXJnZWJuaXNzZSBlcnppZWx0IHdlcmRlbi4gRWluIHBlcmZla3RlciBLbGFzc2lmaWthdG9yIHfDvHJkZSBlaW5lIFJPQy1LdXJ2ZSBoYWJlbiwgZGllIGltIG9iZXJlbiBsaW5rZW4gQmVyZWljaCBiZWdpbm50IHVuZCBuYWNoIHJlY2h0cyBvYmVuIHZlcmzDpHVmdCwgd29iZWkgYWxsZSBGw6RsbGUga29ycmVrdCBrbGFzc2lmaXppZXJ0IHdlcmRlbi4gRWluIHp1ZsOkbGxpZ2VyIEtsYXNzaWZpa2F0b3Igd8O8cmRlIGVpbmUgZGlhZ29uYWwgdmVybGF1ZmVuZGUgUk9DLUt1cnZlIGhhYmVuLCBkYSBkaWUgRlBSIHVuZCBUUFIgenVmw6RsbGlnIHZlcnRlaWx0IHNpbmQuDQoNCkRpZSBBdUMgKEFyZWEgVW5kZXIgdGhlIEN1cnZlKSBpc3QgZWluZSBNZXRyaWssIGRpZSBhdXMgZGVyIFJPQy1LdXJ2ZSBiZXJlY2huZXQgd2lyZCB1bmQgZGllIExlaXN0dW5nIGRlcyBLbGFzc2lmaWthdG9ycyB6dXNhbW1lbmZhc3N0LiBTaWUgZ2lidCBhbiwgd2llIGd1dCBkZXIgS2xhc3NpZmlrYXRvciBpbSBWZXJnbGVpY2ggenUgZWluZW0genVmw6RsbGlnZW4gS2xhc3NpZmlrYXRvciBpc3QuIEVpbmUgQVVDIHZvbiAxIGJlZGV1dGV0LCBkYXNzIGRhcyBNb2RlbGwgcGVyZmVrdCBpbiBkZXIgTGFnZSBpc3QsIHBvc2l0aXZlIHVuZCBuZWdhdGl2ZSBLbGFzc2VuIHp1IHVudGVyc2NoZWlkZW4sIHfDpGhyZW5kIGVpbmUgQVVDIHZvbiAwLjUgYmVkZXV0ZXQsIGRhc3MgZGFzIE1vZGVsbCBrZWluZSBiZXNzZXJlIExlaXN0dW5nIGFscyBadWZhbGwgZXJ6aWVsdC4gRGllIEFVQyBrYW5uIFdlcnRlIHp3aXNjaGVuIDAgdW5kIDEgYW5uZWhtZW4uIEVpbmUgQVVDIHZvbiAwIGJlZGV1dGV0LCBkYXNzIGRhcyBNb2RlbGwgdsO2bGxpZyBpbmtvcnJla3QgaXN0LiBJbSBBbGxnZW1laW5lbiBnaWx0LCBqZSBncsO2w59lciBkaWUgQVVDLCBkZXN0byBiZXNzZXIgaXN0IGRhcyBNb2RlbGwgaW0gVmVyZ2xlaWNoIHp1IGFuZGVyZW4gTW9kZWxsZW4uDQoNCmBgYHtyfQ0KbWFrZV9yb2NfcGxvdF9hbmRfZ2V0X2F1YyA8LSBmdW5jdGlvbihwcmVkaWN0aW9ucywgdHJ1ZV92YWx1ZXMpDQp7DQogIHJvY19jdXJ2ZSA8LSByb2MoYXMubnVtZXJpYyhwcmVkaWN0aW9ucyksIGFzLm51bWVyaWModHJ1ZV92YWx1ZXMpIC0gMSkNCg0KICBwbG90KHJvY19jdXJ2ZSwgeGxhYiA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwgeWxhYiA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLCBtYWluID0iUk9DIEN1cnZlIikNCg0KICBhdWMgPC0gYXVjKHJvY19jdXJ2ZSkNCg0KICByZXR1cm4oYXVjKQ0KfQ0KYGBgDQoNCiMjIyBUaHJlc2hvbGQgUGxvdA0KDQpgYGB7cn0NCg0KZmluZF9iZXN0X3RocmVzaG9sZCA8LSBmdW5jdGlvbihwcmVkaWN0aW9ucywgYWN0dWFsX3ZhbHVlcykgew0KICBiZXN0X3RocmVzaG9sZCA8LSAwDQogIGJlc3RfZjEgPC0gMA0KICB0aHJlc2hvbGRzIDwtIHNlcSgwLjAxLCAwLjk5LCAwLjAxKQ0KICBmMV9zY29yZXMgPC0gcmVwKDAsIGxlbmd0aCh0aHJlc2hvbGRzKSkNCg0KICBmb3IgKGkgaW4gMTpsZW5ndGgodGhyZXNob2xkcykpIHsNCiAgICAjIFNldCB0aGUgdGhyZXNob2xkIGZvciB0aGUgcHJlZGljdGlvbnMNCiAgICBwcmVkaWN0aW9uc190aHJlc2hvbGQgPC0gaWZlbHNlKHByZWRpY3Rpb25zID4gdGhyZXNob2xkc1tpXSwgVFJVRSwgRkFMU0UpDQogIA0KICAgIGlmIChhbGwocHJlZGljdGlvbnNfdGhyZXNob2xkKSkgew0KICAgICAgbmV4dA0KICAgIH0gZWxzZSBpZiAoYWxsKCFwcmVkaWN0aW9uc190aHJlc2hvbGQpKSB7DQogICAgICBuZXh0DQogICAgfQ0KICAgICMgQ2FsY3VsYXRlIHRoZSBmMQ0KICAgIGYxIDwtIGdldF9tZXRyaWNzKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgYWN0dWFsX3ZhbHVlcywgImJlc3QgdGhyZXNob2xkIikkZjENCiAgDQogICAgZjFfc2NvcmVzW2ldIDwtIGYxDQogIA0KICAgICMgVXBkYXRlIHRoZSBiZXN0IHRocmVzaG9sZCBhbmQgYmVzdCBmMSBpZiBuZWNlc3NhcnkNCiAgICBpZiAoZjEgPiBiZXN0X2YxKSB7DQogICAgICBiZXN0X3RocmVzaG9sZCA8LSB0aHJlc2hvbGRzW2ldDQogICAgICBiZXN0X2YxIDwtIGYxDQogICAgfQ0KICB9DQoNCiAgIyBEaXNwbGF5IHRoZSBiZXN0IHRocmVzaG9sZCBhbmQgYmVzdCByZWNhbGwNCiAgcHJpbnQocGFzdGUoIkJlc3QgdGhyZXNob2xkOiIsIGJlc3RfdGhyZXNob2xkKSkNCiAgcHJpbnQocGFzdGUoIkJlc3QgRjE6IiwgYmVzdF9mMSkpDQoNCiAgcGxvdCh0aHJlc2hvbGRzLCBmMV9zY29yZXMsIHR5cGUgPSAibCIsIHhsYWIgPSAiVGhyZXNob2xkIiwgeWxhYiA9ICJGMSBTY29yZSIsIG1haW4gPSAiRjEgU2NvcmUgd2l0aCBkaWZmZXJlbnQgdGhyZXNob2xkcyIpDQoNCiAgcmV0dXJuKGJlc3RfdGhyZXNob2xkKQ0KfQ0KYGBgDQoNCg0KDQojIyBWb3JnZWhlbg0KDQpVbSBlaW4gZ2VlaWduZXRzIE1vZGVsbCB6dSBmaW5kZW4sIHdlcmRlbiB3aXIgdmVyc2NoaWVkZW5lIE1vZGVsbC1BbG9naXJ0aG1lbiBhdWYgZGVuIFRyYWluaW5nc2RhdGVuIGFuenV3ZW5kZW4gdW5kIHZlcnN1Y2hlbiwgZGllc2UgenUgb3B0aW1pZXJlbi4gV2lyIHN0YXJ0ZW4gZGFiZWkgbWl0IGVpbmVtIHZvcmdlZ2ViZW5lbiBCYXNlbGluZSBNb2RlbGwuIEplZGVzIE1vZGVsbCBzY2hhdWVuIHdpciBrdXJ6IGluZGl2aWR1ZWxsIGFuLiBBbSBTY2hsdXNzIHdlcmRlbiBkaWUgdmVyc2NoaWVkZW5lbiBNb2RlbGxlIG5vY2htYWxzIGdlc2FtdGhhZnQgbWl0ZWluYW5kZXIgdmVyZ2xpY2hlbi4gRGFiZWkgc2NoYXVlbiB3aXIgdmVyc2NoaWVkZW5lIE1ldHJpa2VuLCBUb3AtTi1MaXN0ZW4sIEZlYXR1cmUtSW1wb3J0YW5jZSB1bmQgd2VpdGVyZSBJbmZvcm1hdGlvbmVuIHdpZSBkaWUgUk9DLUt1cnZlIG9kZXIgS29uZnVzaW9uc21hdHJpemVuLiBEaWVzZSBJbmZvcm1hdGlvbmVuIGVyaGFsdGVuIHdpciDDvGJlciAxMC1mYWNoZS1LcmV1enZhbGlkaWVydW5nLiBXaXIgdHJhaW5pZXJlbiBhbHNvIGplZGVzIE1vZGVsbCAxMCBNYWwgbWl0IHZlcnNjaGllZGVuZW4gOTAlIGRlciBUcmFpbmluZ3NkYXRlbiB1bmQgYmVyZWNobmVuIHVuc2VyZSBNZXRyaWtlbiB1bmQgd2VpdGVyZW4gSW5mb3JtYXRpb25lbiBhdWYgZGVuIHJlc3RsaWNoZW4gMTAlLg0KDQpEb2NoIHdlbGNoZSBNZXRyaWsgaW4gdW5zZXJlciBMaXN0ZSBpc3QgZGllIHJlbGV2YW50ZXN0ZSBmw7xyIHVucz8gQmVpbSBNb2RlbGwgZ2VodCBlcyBkYXJ1bSwgS3VuZGVuIHp1IGZpbmRlbiwgd2VsY2hlIGFtIGVoZXN0ZW4gZWluZSBLcmVkaXRrYXJ0ZSBrYXVmZW4gd8O8cmRlbi4gRGllc2VuIEt1bmRlbiB3w7xyZGVuIGRhbm4gZsO8ciBkaWUgQmFuayBpbnRlcmVzc2FudCB3ZXJkZW4gdW5kIGVpbmUgZ2V3aXNzZSBNYXNzbmFobWUgd8O8cmRlIGVyZ3JpZmZlbiB3ZXJkZW4sIHNlaSBlcyBkYXMgU2NoaWNrZW4gZWluZXIgV2VyYnVuZyAgb2RlciBkYXMgVW50ZXJicmVpdGVuIGVpbmVzIEFuZ2Vib3RzLiBEYSBkaWVzIG1pdCBlaW5lbSB6ZWl0bGljaGVuIEF1ZndhbmQgdmVyYnVuZGVuIGlzdCB1bmQgS3VuZGVuLCB3ZWxjaGUgZWluIHNvbGNoZXMgQW5nZWJvdCBvZGVyIGVpbmUgV2VyYnVuZyBiZWtvbW1lbiBhYmVyIGtlaW4gSW50ZXJlc3NlIGhhYmVuLCBlaGVyIGdlbmVydnQgZGF2b24gd2VyZGVuIHfDvHJkZW4sIHNvbGxlbiBkaWUgdm9tIE1vZGVsbCBhbHMgcG9zaXRpdiBrbGFzc2lmaXppZXJ0ZW4gRsOkbGxlIG3DtmdsaWNoc3QgdGF0c8OkY2hsaWNoIHBvc2l0aXYgc2Vpbi4gRGFoZXIgc2NoYXVlbiB3aXIgYmVpIGRlbiBNb2RlbGxlbiB2b3IgYWxsZW0gYXVmIGRpZSBQcmVjaXNpb24uDQoNCiMjIyBCYXNlbGluZSBNb2RlbGwNCg0KQWxzIGVyc3RlcyBzb2xsIGVpbmUgbG9naXN0aXNjaGUgUmVncmVzc2lvbiBtaXQgZGVuIEluZm9ybWF0aW9uZW4gQWx0ZXIsIEdlc2NobGVjaHQsIERvbWl6aWxyZWdpb24sIFZlcm3DtmdlbiAoYmFsYW5jZS1TY2huaXR0IMO8YmVyIGFsbGUgTW9uYXRlKSB1bmQgVW1zYXR6IChkaWZmZXJlbmNlLVNjaG5pdHQgw7xiZXIgYWxsZSBNb25hdGUpIGFscyBCYXNlbGluZSBNb2RlbGwgZXJzdGVsbHQgd2VyZGVuLkRhZsO8ciBtw7xzc2VuIHdpciBrdXJ6IGVpbiBuZXVlcyBEYXRhZnJhbWUgZXJzdGVsbGVuLg0KDQpgYGB7cn0NCmJhc2VsaW5lX2RhdGEgPC0gZmluYWxfZGYNCmJhc2VsaW5lX2RhdGEkbWVhbl9iYWxhbmNlIDwtIHJvd01lYW5zKGZpbmFsX2RmWywgYygibWVhbl9iYWxhbmNlXzEiLCAibWVhbl9iYWxhbmNlXzIiLCAibWVhbl9iYWxhbmNlXzMiLCAibWVhbl9iYWxhbmNlXzQiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtZWFuX2JhbGFuY2VfNSIsICJtZWFuX2JhbGFuY2VfNiIsICJtZWFuX2JhbGFuY2VfNyIsICJtZWFuX2JhbGFuY2VfOCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lYW5fYmFsYW5jZV85IiwgIm1lYW5fYmFsYW5jZV8xMCIsICJtZWFuX2JhbGFuY2VfMTEiLCAibWVhbl9iYWxhbmNlXzEyIildKQ0KYmFzZWxpbmVfZGF0YSRtZWFuX2RpZmZlcmVuY2UgPC0gcm93TWVhbnMoZmluYWxfZGZbLCBjKCJtZWFuX2RpZmZlcmVuY2VfMSIsICJtZWFuX2RpZmZlcmVuY2VfMiIsICJtZWFuX2RpZmZlcmVuY2VfMyIsICJtZWFuX2RpZmZlcmVuY2VfNCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibWVhbl9kaWZmZXJlbmNlXzUiLCAibWVhbl9kaWZmZXJlbmNlXzYiLCAibWVhbl9kaWZmZXJlbmNlXzciLCAibWVhbl9kaWZmZXJlbmNlXzgiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lYW5fZGlmZmVyZW5jZV85IiwgIm1lYW5fZGlmZmVyZW5jZV8xMCIsICJtZWFuX2RpZmZlcmVuY2VfMTEiLCAibWVhbl9kaWZmZXJlbmNlXzEyIildKQ0KYmFzZWxpbmVfZGF0YSA8LSBiYXNlbGluZV9kYXRhWywgYygiYWdlIiwgImdlbmRlciIsICJyZWdpb24iLCAiaGFzX2NhcmQiLCAibWVhbl9iYWxhbmNlIiwgIm1lYW5fZGlmZmVyZW5jZSIsICJpc3N1ZWQiKV0NCmJhc2VsaW5lX2RhdGEkaGFzX2NhcmQgPC0gYXMuZmFjdG9yKGJhc2VsaW5lX2RhdGEkaGFzX2NhcmQpDQpgYGANCg0KRGFyYXVmIHdpcmQgZWluIFRyYWluLVRlc3QtU3BsaXQgbWl0IDgwJSBUcmFpbmluZ3NkYXRlbiB1bmQgMjAlIFRlc3RkYXRlbiBnZWJyYXVjaHQuDQogDQpgYGB7cn0NCnNwbGl0cyA8LSBzcGxpdF9kYXRhKGJhc2VsaW5lX2RhdGEsIHRlc3Rfc2l6ZSA9IDAuMikNCnRyYWluIDwtIHNwbGl0cyR0cmFpbg0KdGVzdCA8LSBzcGxpdHMkdGVzdA0KdHJhaW5fc2V0cyA8LSBzcGxpdHMkdHJhaW5fc2V0cw0KdmFsX3NldHMgPC0gc3BsaXRzJHZhbF9zZXRzDQpgYGANCg0KRGFzIFJlZ3Jlc3Npb25zbW9kZWxsIHdpcmQgYXVmIGRlbiBUcmFpbmluZ3NkYXRlbiB0cmFpbmllcnQuIFdpciB2ZXJ3ZW5kZW4gZGllIEZ1bmt0aW9uIGdsbSB2b24gZGVyIExpYnJhcnkgInN0YXRzIi4gQmVpIGplZGVtIE1vZGVsbCBtw7xzc2VuIGRpZSBQcmVkaWN0aW9ucyBnZW1hY2h0IHVuZCBkYW1pdCBkaWUgTWV0cmlrZW4gZXJzdGVsbHQgd2VyZGVuLiBEYSBkaWUgbG9naXN0aXNjaGUgUmVncmVzc2lvbiBlaW5lIFphaGwgendpc2NoZW4gMCB1bmQgMSB6dXLDvGNrZ2lidCwgbcO8c3NlbiB3aXIgYW5oYW5kIGVpbmVzIFRocmVzaG9sZHMgZGllIFdlcnRlIHp1IFRSVUUgdW5kIEZBTFNFIHVtd2FuZGVsbi4gRGVyIFRocmVzaG9sZCBnaWJ0IGFuLCBhYiB3ZWxjaGVtIFdlcnQgZWluZSBWb3JoZXJzYWdlIGFscyBwb3NpdGl2IGJldHJhY2h0ZXQgd2lyZC4gU3RhbmRhcmRtw6TDn2lnIGlzdCBkZXIgVGhyZXNob2xkIGF1ZiAwLjUgZ2VzZXR6dCwgd2FzIGJlZGV1dGV0LCBkYXNzIGFsbGUgS3VuZGVuIG1pdCBlaW5lbSBXZXJ0IGdyw7Zzc2VyIGFscyAwLjUgYWxzIEthcnRlbmvDpHVmZXIgYmV0cmFjaHRldCB3ZXJkZW4uRGllIFdlcnRlIGbDvHIgamVkZSBWYWxpZGllcnVuZyB3ZXJkZW4gaW4gZWluZXIgTGlzdGUgZ2VzcGVpY2hlcnQuIEFscyBlcnN0ZXMgd29sbGVuIHdpciB1bnMgZGllIGR1cmNoc2Nobml0dGxpY2hlbiBNZXRyaWtlbiBhbnNjaGF1ZW4uDQoNCmBgYHtyfQ0KcmVncmVzc2lvbl9iYXNlbGluZV9tZXRyaWNzIDwtIGxpc3QoKQ0KcmVncmVzc2lvbl9iYXNlbGluZV9wcmVkaWN0aW9ucyA8LSBsaXN0KCkNCnJlZ3Jlc3Npb25fYmFzZWxpbmVfcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGxpc3QoKQ0KDQpmb3IoaSBpbiAxOmxlbmd0aCh2YWxfc2V0cykpIHsNCiAgDQogIHJlZ3Jlc3Npb25fYmFzZWxpbmUgPC0gZ2xtKGhhc19jYXJkIH4gLiwgZGF0YSA9IHRyYWluX3NldHNbW2ldXSwgZmFtaWx5ID0gYmlub21pYWwpDQogIA0KICByZWdyZXNzaW9uX2Jhc2VsaW5lX3ByZWRpY3Rpb25zW1tpXV0gPC0gcHJlZGljdChyZWdyZXNzaW9uX2Jhc2VsaW5lLCB2YWxfc2V0c1tbaV1dLCB0eXBlID0gInJlc3BvbnNlIikNCiAgDQogIHRocmVzaG9sZCA8LSAwLjUNCiAgDQogIHJlZ3Jlc3Npb25fYmFzZWxpbmVfcHJlZGljdGlvbnNfdGhyZXNob2xkW1tpXV0gPC0gaWZlbHNlKHJlZ3Jlc3Npb25fYmFzZWxpbmVfcHJlZGljdGlvbnNbW2ldXSA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQoNCiAgcmVncmVzc2lvbl9iYXNlbGluZV9tZXRyaWNzW1tpXV0gPC0gZ2V0X21ldHJpY3MocmVncmVzc2lvbl9iYXNlbGluZV9wcmVkaWN0aW9uc190aHJlc2hvbGRbW2ldXSwgdmFsX3NldHNbW2ldXSRoYXNfY2FyZCwgIkxvZ2lzdGljIFJlZ3Jlc3Npb24gQmFzZWxpbmUiKQ0KICANCn0NCg0KcmVncmVzc2lvbl9iYXNlbGluZV9tZXRyaWNzIDwtIGRvLmNhbGwoInJiaW5kIiwgcmVncmVzc2lvbl9iYXNlbGluZV9tZXRyaWNzKQ0KbWV0cmljc19hbGwgPC0gcmVncmVzc2lvbl9iYXNlbGluZV9tZXRyaWNzDQoNCmNvbE1lYW5zKHJlZ3Jlc3Npb25fYmFzZWxpbmVfbWV0cmljcyAlPiUgc2VsZWN0KC1tb2RlbCkpDQoNCiNyZWdyZXNzaW9uX2Jhc2VsaW5lX2V4cGxhaW5lciA8LSBleHBsYWluKHJlZ3Jlc3Npb25fYmFzZWxpbmUsIGRhdGEgPSB0cmFpbikNCmBgYA0KDQpVbSBkaWUgS29uZnVzaW9uc21hdHJpeCBhbnp1emVpZ2VuIGJlcmVjaG5lbiB3aXIgZGVuIFNjaG5pdHQgZGVzIFdlcnRlcyBkZXIgbG9naXN0aXNjaGVuIFJlZ3Jlc3Npb24gZsO8ciBqZWRlbiBLdW5kZW4gDQoNCmBgYHtyfQ0KI3Bsb3RfY29uZnVzaW9uX21hdHJpeChyZWdyZXNzaW9uX2Jhc2VsaW5lX3ByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQoNCmBgYHtyfQ0KI21ha2Vfcm9jX3Bsb3RfYW5kX2dldF9hdWMocmVncmVzc2lvbl9iYXNlbGluZV9wcmVkaWN0aW9ucywgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQojIyMgUmVncmVzc2lvbnNtb2RlbGwgbWl0IGFsbGVuIERhdGVuDQoNCk51biBzb2xsIGRpZXNlcyBCYXNlbGluZS1Nb2RlbGwgdmVyYmVzc2VydCB3ZXJkZW4uIEFscyBlcnN0ZXMgcHJvYmllcmVuIHdpciwgZGFzIGdsZWljaGUgTW9kZWxsIChMb2dpc3Rpc2NoZSBSZWdyZXNzaW9uKSBtaXQgbWVociBJbnB1dC1QYXJhbWV0ZXJuIHp1IHRyYWluaWVyZW4uIA0KDQpEYSBkaWUgbG9naXN0aXNjaGUgUmVncmVzc2lvbiBQcm9ibGVtZSBtaXQgRmFrdG9yZW4gaGF0LCB3ZWxjaGUgbnVyIGltIFRyYWluaW5ncy0gYnp3LiBUZXN0ZGF0ZW5zYXR6IHZvcmtvbW1lbiB1bmQgYXVjaCBuaWNodCBndXQgbWl0IE5BJ3MgdW1nZWhlbiBrYW5uLCBtw7xzc2VuIHdpciB6dWVyc3Qgbm9jaCBlaW5pZ2UgQW5wYXNzdW5nZW4gYW0gRGF0ZW5zYXR6IHZvcm5laG1lbi4NCg0KQWxzIGVyc3RlcyBlbnRmZXJuZW4gd2lyIGFsbGUgS29sb25uZW4sIHdlbGNoZSBGYWt0b3JlbiBzaW5kIHVuZCBtZWhyIGFscyAxMCB2ZXJzY2hpZWRlbmUgQXVzcHLDpGd1bmdlbiBoYWJlbi4NCg0KYGBge3J9DQojIEVybWl0dGxlIGRpZSBudW1lcmlzY2hlbiBNZXJrbWFsZSBpbiBkZW4gVHJhaW5pbmdzZGF0ZW4NCm51bWVyaWNfdmFycyA8LSBzYXBwbHkoZmluYWxfZGYsIGlzLm51bWVyaWMpDQoNCiMgRXJzdGVsbGUgZWluIFN1YnNldCBkZXIgVHJhaW5pbmdzZGF0ZW4gb2huZSBkaWUgbnVtZXJpc2NoZW4gTWVya21hbGUNCnRyYWluX25vX251bWVyaWMgPC0gZmluYWxfZGZbLCAhbnVtZXJpY192YXJzXQ0KDQojIEVybWl0dGxlIGRpZSBBbnphaGwgZGVyIEthdGVnb3JpZW4gZsO8ciBqZWRlcyBNZXJrbWFsDQpudW1fY2F0ZWdvcmllcyA8LSBzYXBwbHkodHJhaW5fbm9fbnVtZXJpYywgZnVuY3Rpb24oeCkgbGVuZ3RoKHVuaXF1ZSh4KSkpDQoNCiMgw5xiZXJwcsO8ZmUsIG9iIGVpbiBNZXJrbWFsIHp1IHZpZWxlIEthdGVnb3JpZW4gaGF0DQp0b29fbWFueV9jYXRlZ29yaWVzIDwtIG51bV9jYXRlZ29yaWVzID4gMTANCg0KIyBHaWIgZGllIE5hbWVuIGRlciBNZXJrbWFsZSBhdXMsIGRpZSB6dSB2aWVsZSBLYXRlZ29yaWVuIGhhYmVuDQpjb2x1bW5zX3RvX3JlbW92ZSA8LSBjb2xuYW1lcyh0cmFpbl9ub19udW1lcmljKVt0b29fbWFueV9jYXRlZ29yaWVzXQ0KDQoNCiMgRXJtaXR0bGUgZGllIFNwYWx0ZW5uYW1lbiwgZGllIGJlaGFsdGVuIHdlcmRlbiBzb2xsZW4NCmtlZXBfY29sdW1ucyA8LSBzZXRkaWZmKGNvbG5hbWVzKGZpbmFsX2RmKSwgY29sdW1uc190b19yZW1vdmUpDQoNCiMgRXJzdGVsbGUgZWluIFN1YnNldCBkZXMgRGF0YWZyYW1lcyBtaXQgZGVuIGJlaGFsdGVuZW4gU3BhbHRlbm5hbWVuDQpmaW5hbF9kZl9zaW1wbGlmaWVkIDwtIGZpbmFsX2RmWywga2VlcF9jb2x1bW5zXQ0KDQpjb2x1bW5zX3RvX3JlbW92ZQ0KYGBgDQoNCk51biBtdXNzIGRlciBEYXRlbnNhdHogbm9jaCBhdWYgTkEncyDDvGJlcnByw7xmdCB3ZXJkZW4uDQoNCmBgYHtyfQ0KIyBDb3VudCB0aGUgbnVtYmVyIG9mIE5BIHZhbHVlcyBpbiB0aGUgZGF0YSBmcmFtZQ0KbnVtX25hIDwtIHN1bShpcy5uYShmaW5hbF9kZl9zaW1wbGlmaWVkKSkNCg0KIyBQcmludCB0aGUgdG90YWwgbnVtYmVyIG9mIE5BIHZhbHVlcw0KcHJpbnQocGFzdGUoIlRvdGFsIG51bWJlciBvZiBOQSB2YWx1ZXM6IiwgbnVtX25hKSkNCg0KIyBDcmVhdGUgYSBsb2dpY2FsIHZlY3RvciBpbmRpY2F0aW5nIHdoZXRoZXIgZWFjaCBlbGVtZW50IGlzIE5BDQpuYV9tYXRyaXggPC0gaXMubmEoZmluYWxfZGZfc2ltcGxpZmllZCkNCg0KIyBTdW0gdGhlIG51bWJlciBvZiBOQSB2YWx1ZXMgcGVyIHJvdw0KbmFfY291bnRzIDwtIHJvd1N1bXMobmFfbWF0cml4KQ0KDQojIENvdW50IHRoZSByb3dzIHdpdGggTkEgdmFsdWVzDQpudW1fbmFfcm93cyA8LSBzdW0obmFfY291bnRzID4gMCkNCg0KIyBQcmludCB0aGUgbnVtYmVyIG9mIHJvd3Mgd2l0aCBOQSB2YWx1ZXMNCnByaW50KHBhc3RlKCJOdW1iZXIgb2Ygcm93cyB3aXRoIE5BIHZhbHVlczoiLCBudW1fbmFfcm93cykpDQoNCiMgU3VtIHRoZSBudW1iZXIgb2YgTkEgdmFsdWVzIHBlciBjb2x1bW4NCm5hX2NvdW50c19jb2xzIDwtIGNvbFN1bXMoKG5hX21hdHJpeCkpDQoNCiMgQ291bnQgdGhlIGNvbHVtbnMgd2l0aCBOQSB2YWx1ZXMNCm51bV9uYV9jb2xzIDwtIHN1bShuYV9jb3VudHNfY29scyA+IDApDQoNCiMgUHJpbnQgdGhlIG51bWJlciBvZiBjb2x1bW5zIHdpdGggTkEgdmFsdWVzDQpwcmludChwYXN0ZSgiTnVtYmVyIG9mIGNvbHVtbnMgd2l0aCBOQSB2YWx1ZXM6IiwgbnVtX25hX2NvbHMpKQ0KYGBgDQoNCkZhc3QgamVkZSBaZWlsZSBoYXQgaXJnZW5kd28gZWluIE5BLiBFcyBzaW5kIGF1Y2ggdmllbGUgS29sb25uZW4gYmV0cm9mZmVuLiBXaXIga8O2bm5lbiBhbHNvIG5pY2h0IGFsbGUgT2JzZXJ2YXRpb25lbiBvZGVyIFZhcmlhYmVsbiBtaXQgTkEncyBlbnRmZXJuZW4sIGRhIHNvbnN0IGRlciBEYXRlbnZlcmx1c3Qgc2VociBncm9zcyB3w6RyZS4gRGFoZXIgaW1wdXRpZXJlbiB3aXIgZGllIG51bWVyaXNjaGVuIGZlaGxlbmRlbiBXZXJ0ZSBtaXQgZGVtIE1lZGlhbiB1bmQgZGllIGZlaGxlbmRlbiBrYXRlZ29yaWFsZW4gV2VydGUgbWl0IGRlbSBXZXJ0LCB3ZWxjaGVyIGFtIG1laXN0ZW4gdm9ya29tbXQuDQoNCmBgYHtyfQ0KIyBJbXB1dGUgTkEgbnVtYmVycyB3aXRoIHRoZSBtZWRpYW4NCmZpbmFsX2RmX3NpbXBsaWZpZWQgPC0gZmluYWxfZGZfc2ltcGxpZmllZCAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBsaXN0KH4gaWZfZWxzZShpcy5uYSguKSwgbWVkaWFuKC4sIG5hLnJtID0gVFJVRSksIGFzLmRvdWJsZSguKSkpKQ0KDQojIEltcHV0ZSBOQSBzdHJpbmdzL2ZhY3RvcnMgd2l0aCB0aGUgbW9zdCBjb21tb24gdmFsdWUNCmZpbmFsX2RmX3NpbXBsaWZpZWQgPC0gZmluYWxfZGZfc2ltcGxpZmllZCAlPiUgDQogIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGxpc3QofiBpZl9lbHNlKGlzLm5hKC4pLCBtb2RlKC4pLCAuKSkpDQpgYGANCg0KV2lyIMO8YmVycHLDvGZlbiBub2NobWFscywgb2IgZXMga2VpbmUgZmVobGVuZGVuIFdlcnRlIG1laHIgZ2lidC4NCg0KYGBge3J9DQpzdW0oaXMubmEoZmluYWxfZGZfc2ltcGxpZmllZCkpDQpgYGANCg0KRGEgZGFzIEthdWZkYXR1bSBmw7xyIGRlbiBTcGxpdCBiZW7DtnRpZ3Qgd2lyZCBhYmVyIHZvcmhlciBlbnRmZXJudCB3dXJkZSwgZGEgZXMgbWVociBhbHMgMTAgdmVyc2NoaWVkZW5lIEF1c3Byw6RndW5nZW4gYW5uZWhtZW4ga2FubiwgZsO8Z2VuIHdpciBlcyBoaWVyIG5vY2htYWxzIHp1bSBuZXVlbiBEYXRlbnNhdHogaGluenUuDQoNCmBgYHtyfQ0KZmluYWxfZGZfc2ltcGxpZmllZCRpc3N1ZWQgPC0gZmluYWxfZGYkaXNzdWVkDQpgYGANCg0KDQpgYGB7cn0NCnNwbGl0cyA8LSBzcGxpdF9kYXRhKGZpbmFsX2RmX3NpbXBsaWZpZWQsIHRlc3Rfc2l6ZSA9IDAuMikNCnRyYWluIDwtIHNwbGl0cyR0cmFpbg0KdGVzdCA8LSBzcGxpdHMkdGVzdA0KdHJhaW5fc2V0cyA8LSBzcGxpdHMkdHJhaW5fc2V0cw0KdmFsX3NldHMgPC0gc3BsaXRzJHZhbF9zZXRzDQpgYGANCg0KYGBge3J9DQpyZWdyZXNzaW9uX2FsbF9tZXRyaWNzIDwtIGxpc3QoKQ0KcmVncmVzc2lvbl9hbGxfcHJlZGljdGlvbnMgPC0gbGlzdCgpDQpyZWdyZXNzaW9uX2FsbF9wcmVkaWN0aW9uc190aHJlc2hvbGQgPC0gbGlzdCgpDQoNCmZvcihpIGluIDE6bGVuZ3RoKHZhbF9zZXRzKSkgew0KICANCiAgcmVncmVzc2lvbl9hbGwgPC0gZ2xtKGhhc19jYXJkIH4gLiwgZGF0YSA9IHRyYWluX3NldHNbW2ldXSwgZmFtaWx5ID0gYmlub21pYWwpDQogIA0KICByZWdyZXNzaW9uX2FsbF9wcmVkaWN0aW9uc1tbaV1dIDwtIHByZWRpY3QocmVncmVzc2lvbl9hbGwsIHZhbF9zZXRzW1tpXV0sIHR5cGUgPSAicmVzcG9uc2UiKQ0KICANCiAgdGhyZXNob2xkIDwtIDAuNQ0KICANCiAgcmVncmVzc2lvbl9hbGxfcHJlZGljdGlvbnNfdGhyZXNob2xkW1tpXV0gPC0gaWZlbHNlKHJlZ3Jlc3Npb25fYWxsX3ByZWRpY3Rpb25zW1tpXV0gPiB0aHJlc2hvbGQsIFRSVUUsIEZBTFNFKQ0KDQogIHJlZ3Jlc3Npb25fYWxsX21ldHJpY3NbW2ldXSA8LSBnZXRfbWV0cmljcyhyZWdyZXNzaW9uX2FsbF9wcmVkaWN0aW9uc190aHJlc2hvbGRbW2ldXSwgdmFsX3NldHNbW2ldXSRoYXNfY2FyZCwgIkxvZ2lzdGljIFJlZ3Jlc3Npb24gQWxsIikNCiAgDQp9DQoNCnJlZ3Jlc3Npb25fYWxsX21ldHJpY3MgPC0gZG8uY2FsbCgicmJpbmQiLCByZWdyZXNzaW9uX2FsbF9tZXRyaWNzKQ0KbWV0cmljc19hbGwgPC0gYmluZF9yb3dzKG1ldHJpY3NfYWxsLCByZWdyZXNzaW9uX2FsbF9tZXRyaWNzKQ0KDQpjb2xNZWFucyhyZWdyZXNzaW9uX2FsbF9tZXRyaWNzICU+JSBzZWxlY3QoLW1vZGVsKSkNCmBgYA0KDQoNCmBgYHtyfQ0KDQojcmVncmVzc2lvbl9hbGxfZXhwbGFpbmVyIDwtIGV4cGxhaW4ocmVncmVzc2lvbl9hbGwsIGRhdGEgPSB0cmFpbikNCmBgYA0KDQoNCmBgYHtyfQ0KI3Bsb3RfY29uZnVzaW9uX21hdHJpeChyZWdyZXNzaW9uX2FsbF9wcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQpgYGANCg0KYGBge3J9DQojbWFrZV9yb2NfcGxvdF9hbmRfZ2V0X2F1YyhyZWdyZXNzaW9uX2FsbF9wcmVkaWN0aW9ucywgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQoNCmBgYHtyfQ0KI3Bsb3RfZmVhdHVyZV9pbXBvcnRhbmNlKHJlZ3Jlc3Npb25fYWxsKQ0KYGBgDQoNCiMjIyBEZWNpc2lvbiBUcmVlDQoNCkbDvHIgZGVuIERlY2lzb24gVHJlZSB2ZXJ3ZW5kZW4gd2lyIGRhcyBQYWNrYWdlICJycGFydCIuDQoNCmBgYHtyfQ0KZGVjaXNpb25fdHJlZV9tZXRyaWNzIDwtIGxpc3QoKQ0KZGVjaXNpb25fdHJlZV9wcmVkaWN0aW9ucyA8LSBsaXN0KCkNCmRlY2lzaW9uX3RyZWVfcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGxpc3QoKQ0KDQpmb3IoaSBpbiAxOmxlbmd0aCh2YWxfc2V0cykpIHsNCiAgDQogIGRlY2lzaW9uX3RyZWUgPC0gcnBhcnQoaGFzX2NhcmQgfiAuLCBkYXRhID0gdHJhaW5fc2V0c1tbaV1dLCBtZXRob2QgPSAiY2xhc3MiKQ0KICANCiAgZGVjaXNpb25fdHJlZV9wcmVkaWN0aW9uc1tbaV1dIDwtIHByZWRpY3QoZGVjaXNpb25fdHJlZSwgdmFsX3NldHNbW2ldXSwgdHlwZSA9ICJwcm9iIilbLCAyXQ0KICANCiAgdGhyZXNob2xkIDwtIDAuNQ0KICANCiAgZGVjaXNpb25fdHJlZV9wcmVkaWN0aW9uc190aHJlc2hvbGRbW2ldXSA8LSBpZmVsc2UoZGVjaXNpb25fdHJlZV9wcmVkaWN0aW9uc1tbaV1dID4gdGhyZXNob2xkLCBUUlVFLCBGQUxTRSkNCg0KICBkZWNpc2lvbl90cmVlX21ldHJpY3NbW2ldXSA8LSBnZXRfbWV0cmljcyhkZWNpc2lvbl90cmVlX3ByZWRpY3Rpb25zX3RocmVzaG9sZFtbaV1dLCB2YWxfc2V0c1tbaV1dJGhhc19jYXJkLCAiRGVjaXNpb24gVHJlZSIpDQogIA0KfQ0KDQpkZWNpc2lvbl90cmVlX21ldHJpY3MgPC0gZG8uY2FsbCgicmJpbmQiLCBkZWNpc2lvbl90cmVlX21ldHJpY3MpDQptZXRyaWNzX2FsbCA8LSBiaW5kX3Jvd3MobWV0cmljc19hbGwsIGRlY2lzaW9uX3RyZWVfbWV0cmljcykNCg0KY29sTWVhbnMoZGVjaXNpb25fdHJlZV9tZXRyaWNzICU+JSBzZWxlY3QoLW1vZGVsKSkNCmBgYA0KDQpgYGB7cn0NCiNwbG90X2NvbmZ1c2lvbl9tYXRyaXgoZGVjaXNpb25fdHJlZV9wcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQojbWFrZV9yb2NfcGxvdF9hbmRfZ2V0X2F1YyhkZWNpc2lvbl90cmVlX3ByZWRpY3Rpb25zLCB0ZXN0JGhhc19jYXJkKQ0KI3ByZWRpY3Rpb25zX3RyYWluIDwtIHByZWRpY3QoZGVjaXNpb25fdHJlZSwgdHJhaW4sIHR5cGUgPSAicHJvYiIpWywgMl0NCiNiZXN0X3RocmVzaG9sZCA8LSBmaW5kX2Jlc3RfdGhyZXNob2xkKHByZWRpY3Rpb25zX3RyYWluLCB0cmFpbiRoYXNfY2FyZCkNCiNycGFydC5wbG90KGRlY2lzaW9uX3RyZWUpDQpgYGANCg0KDQpEaWUgS2xhc3NpZmlrYXRpb24gaXN0IHVtIGVpbmlnZXMgYmVzc2VyIGF1ZiBkZW0gRGVjaXNpb24gVHJlZS4gRXMgc2NoZWludCBhbHNvIGbDvHIgdW5zZXJlbiBWZXJ3ZW5kdW5nc3p3ZWNrIGRlciBiZXNzZXJlIEFsZ29yaXRobXVzIHp1IHNlaW4uIFdpciB1bnRlcnN1Y2hlbiBub2NoIEVyd2VpdGVydW5nZW4gZGVzIERlY2lzaW9uIFRyZWVzOiBkZXIgUmFuZG9tIEZvcmVzdC4NCg0KDQoNCiMjIyBSYW5kb20gRm9yZXN0IA0KDQpGw7xyIGRlbiBSYW5kb20gRm9yZXN0IGJyYXVjaGVuIHdpciBkYXMgUGFja2FnZSAicmFuZG9tRm9yZXN0Ii4gRXMgdmVyd2VuZGV0IEJyZWltYW4ncyByYW5kb20gZm9yZXN0IGFsZ29yaXRobXVzIHVuZCBrYW5uIGbDvHIgS2xhc3NpZmlrYXRpb24gdW5kIFJlZ3Jlc3Npb24gdmVyd2VuZGV0IHdlcmRlbi4NCg0KQmVpbSBSYW5kb20gRm9yZXN0IG11c3MgdW5zZXJlIFppZWx2YXJpYWJlbCBub2NoIGluIGVpbmVuIEZha3RvciB1bWdld2FuZGVsdCB3ZXJkZW4sIGRhbWl0IGRpZSBXYWhyc2NoZWlubGljaGtlaXRlbiB2b3JoZXJnZXNhZ3Qgd2VyZGVuIGvDtm5uZW4uDQoNCmBgYHtyfQ0KcmFuZG9tX2ZvcmVzdF9tZXRyaWNzIDwtIGxpc3QoKQ0KcmFuZG9tX2ZvcmVzdF9wcmVkaWN0aW9ucyA8LSBsaXN0KCkNCnJhbmRvbV9mb3Jlc3RfcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGxpc3QoKQ0KDQpmb3IoaSBpbiAxOmxlbmd0aCh2YWxfc2V0cykpIHsNCiAgDQogIHRyYWluX3NldHNbW2ldXSRoYXNfY2FyZCA8LSBhcy5mYWN0b3IodHJhaW5fc2V0c1tbaV1dJGhhc19jYXJkKQ0KICANCiAgcmFuZG9tX2ZvcmVzdCA8LSByYW5kb21Gb3Jlc3QoaGFzX2NhcmQgfiAuLCBkYXRhID0gdHJhaW5fc2V0c1tbaV1dLCBtZXRob2QgPSAiY2xhc3MiKQ0KICANCiAgcmFuZG9tX2ZvcmVzdF9wcmVkaWN0aW9uc1tbaV1dIDwtIHByZWRpY3QocmFuZG9tX2ZvcmVzdCwgdmFsX3NldHNbW2ldXSwgdHlwZSA9ICJwcm9iIilbLCAyXQ0KICANCiAgdGhyZXNob2xkIDwtIDAuNQ0KICANCiAgcmFuZG9tX2ZvcmVzdF9wcmVkaWN0aW9uc190aHJlc2hvbGRbW2ldXSA8LSBpZmVsc2UocmFuZG9tX2ZvcmVzdF9wcmVkaWN0aW9uc1tbaV1dID4gdGhyZXNob2xkLCBUUlVFLCBGQUxTRSkNCg0KICByYW5kb21fZm9yZXN0X21ldHJpY3NbW2ldXSA8LSBnZXRfbWV0cmljcyhyYW5kb21fZm9yZXN0X3ByZWRpY3Rpb25zX3RocmVzaG9sZFtbaV1dLCB2YWxfc2V0c1tbaV1dJGhhc19jYXJkLCAiUmFuZG9tIEZvcmVzdCIpDQogIA0KfQ0KDQpyYW5kb21fZm9yZXN0X21ldHJpY3MgPC0gZG8uY2FsbCgicmJpbmQiLCByYW5kb21fZm9yZXN0X21ldHJpY3MpDQptZXRyaWNzX2FsbCA8LSBiaW5kX3Jvd3MobWV0cmljc19hbGwsIHJhbmRvbV9mb3Jlc3RfbWV0cmljcykNCg0KY29sTWVhbnMocmFuZG9tX2ZvcmVzdF9tZXRyaWNzICU+JSBzZWxlY3QoLW1vZGVsKSkNCmBgYA0KRGVyIFJhbmRvbSBGb3Jlc3QgaXN0IGVpbiB3ZW5pZyBiZXNzZXIgYWxzIGRlciBEZWNpc2lvbiBUcmVlLiBBbHMgbsOkY2hzdGVzIHdvbGxlbiB3aXIgcHJvYmllcmVuLCBvYiBlaW5lIEh5cGVycGFyYW1ldGVyb3B0aW1pZXJ1bmcgdW5zZXIgUmVzdWx0YXQgbm9jaCB2ZXJiZXNzZXJuIGthbm4uDQoNCg0KYGBge3J9DQojcGxvdF9jb25mdXNpb25fbWF0cml4KHJhbmRvbV9mb3Jlc3RfcHJlZGljdGlvbnNfdGhyZXNob2xkLCB0ZXN0JGhhc19jYXJkKQ0KI21ha2Vfcm9jX3Bsb3RfYW5kX2dldF9hdWMocmFuZG9tX2ZvcmVzdF9wcmVkaWN0aW9ucywgdGVzdCRoYXNfY2FyZCkNCiNwcmVkaWN0aW9uc190cmFpbiA8LSBwcmVkaWN0KHJhbmRvbV9mb3Jlc3QsIHRyYWluLCB0eXBlID0gInByb2IiKVssIDJdDQojYmVzdF90aHJlc2hvbGQgPC0gZmluZF9iZXN0X3RocmVzaG9sZChwcmVkaWN0aW9uc190cmFpbiwgdHJhaW4kaGFzX2NhcmQpDQpgYGANCg0KIyMjIFhHQm9vc3QNCg0KRGFzIFhHQm9vc3QgTW9kZWxsIHdpcmQgbWl0IGRlciBMaWJyYXJ5ICJ4Z2Jvb3N0IiB0cmFpbmllcnQuIERhIGVzIFByb2JsZW1lIG1pdCBuaWNodC1udW1lcmlzY2hlbiBWYXJpYWJlbG4gZ2FiLCB3dXJkZW4gZGllc2UgZW50ZmVybnQuDQoNCg0KYGBge3J9DQp4Z19ib29zdF9tZXRyaWNzIDwtIGxpc3QoKQ0KeGdfYm9vc3RfcHJlZGljdGlvbnMgPC0gbGlzdCgpDQp4Z19ib29zdF9wcmVkaWN0aW9uc190aHJlc2hvbGQgPC0gbGlzdCgpDQoNCmZvcihpIGluIDE6bGVuZ3RoKHZhbF9zZXRzKSkgew0KICANCiAgdHJhaW5fZGF0YSA8LSB0cmFpbl9zZXRzW1tpXV0NCiAgdGVzdF9kYXRhIDwtIHZhbF9zZXRzW1tpXV0NCiAgDQogIHRyYWluX2RhdGFbXSA8LSBsYXBwbHkodHJhaW5fc2V0c1tbaV1dLCBhcy5udW1lcmljKQ0KICB0ZXN0X2RhdGFbXSA8LSBsYXBwbHkodmFsX3NldHNbW2ldXSwgYXMubnVtZXJpYykNCg0KICANCiAgdHJhaW5fZGF0YSA8LSBjYmluZChhcy5udW1lcmljKHRyYWluX3NldHNbW2ldXSRoYXNfY2FyZCksIHRyYWluX2RhdGEpDQogIHRlc3RfZGF0YSA8LSBjYmluZChhcy5udW1lcmljKHZhbF9zZXRzW1tpXV0kaGFzX2NhcmQpLCB0ZXN0X2RhdGEpDQogIA0KICB0cmFpbl9wcmVkcyA8LSBhcy5tYXRyaXgoc2FwcGx5KHRyYWluX2RhdGFbLC0xXSwgYXMubnVtZXJpYykpDQogIHRyYWluX2xhYmVscyA8LSB0cmFpbl9kYXRhWywgMV0NCiAgDQogIHRlc3RfcHJlZHMgPC0gYXMubWF0cml4KHNhcHBseSh0ZXN0X2RhdGFbLC0xXSwgYXMubnVtZXJpYykpDQogIHRlc3RfbGFiZWxzIDwtIHRlc3RfZGF0YVssIDFdDQogIA0KICBwYXJhbSA8LSBsaXN0KG1heF9kZXB0aCA9IDEwMCwgZXRhID0gMC4xLCBudGhyZWFkID0gMikNCg0KICAjIFRyYWluIHRoZSBtb2RlbA0KICB4Z19ib29zdCA8LSB4Z2Jvb3N0KGRhdGEgPSB0cmFpbl9wcmVkcywgbGFiZWwgPSB0cmFpbl9sYWJlbHMgLSAxLCBucm91bmRzID0gMTAwLCBvYmplY3RpdmUgPSAiYmluYXJ5OmxvZ2lzdGljIiwgdmVyYm9zZSA9IDAsIG1heGltaXplID0gInByZWNpc2lvbiIsIHBhcmFtcyA9IHBhcmFtKQ0KDQogICMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhDQogIHhnX2Jvb3N0X3ByZWRpY3Rpb25zW1tpXV0gPC0gcHJlZGljdCh4Z19ib29zdCwgdGVzdF9wcmVkcykgDQogIA0KICB0aHJlc2hvbGQgPC0gMC41DQogIA0KICB4Z19ib29zdF9wcmVkaWN0aW9uc190aHJlc2hvbGRbW2ldXSA8LSBpZmVsc2UoeGdfYm9vc3RfcHJlZGljdGlvbnNbW2ldXSA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQogIA0KICB4Z19ib29zdF9tZXRyaWNzW1tpXV0gPC0gZ2V0X21ldHJpY3MoeGdfYm9vc3RfcHJlZGljdGlvbnNfdGhyZXNob2xkW1tpXV0sIHZhbF9zZXRzW1tpXV0kaGFzX2NhcmQsICJYR0Jvb3N0IikNCiAgDQp9DQoNCnhnX2Jvb3N0X21ldHJpY3MgPC0gZG8uY2FsbCgicmJpbmQiLCB4Z19ib29zdF9tZXRyaWNzKQ0KbWV0cmljc19hbGwgPC0gYmluZF9yb3dzKG1ldHJpY3NfYWxsLCB4Z19ib29zdF9tZXRyaWNzKQ0KDQpjb2xNZWFucyh4Z19ib29zdF9tZXRyaWNzICU+JSBzZWxlY3QoLW1vZGVsKSkNCmBgYA0KDQoNCg0KDQpgYGB7cn0NCmltcG9ydGFuY2VfdmFsdWVzIDwtIHZhckltcChyYW5kb21fZm9yZXN0KQ0KaW1wb3J0YW5jZV92YWx1ZXMkdmFyaWFibGUgPC0gcm93bmFtZXModmFySW1wKHJhbmRvbV9mb3Jlc3QpKSANCmltcG9ydGFuY2VfdmFsdWVzIDwtIGltcG9ydGFuY2VfdmFsdWVzW29yZGVyKGltcG9ydGFuY2VfdmFsdWVzJE92ZXJhbGwsIGRlY3JlYXNpbmcgPSBUUlVFKSxdDQoNCiMgV8OkaGxlbiBTaWUgZGllIHRvcCAxMCBNZXJrbWFsZSBhdXMNCnRvcF9mZWF0dXJlcyA8LSBoZWFkKGltcG9ydGFuY2VfdmFsdWVzLCBuID0gMTApDQp0b3BfZmVhdHVyZXMNCg0KYGBgDQoNClRPRE86IENIRUNLIGlmIGlmZWxzZSBpcyBjb3JyZWN0DQoNCmBgYHtyfQ0KIyBJbml0aWFsaXplIGVtcHR5IHZlY3RvciB0byBzdG9yZSBmMS1zY29yZXMNCnNldC5zZWVkKDI3KQ0KZjFfc2NvcmVzIDwtIGMoKQ0KDQojIExvb3AgdGhyb3VnaCBlYWNoIHZhcmlhYmxlIGFuZCBmaXQgYSByYW5kb20gZm9yZXN0IG1vZGVsDQpmb3IgKGkgaW4gMTo1MCkgew0KICAjIFNlbGVjdCBzdWJzZXQgb2YgdmFyaWFibGVzDQogIHZhcnNfc3Vic2V0IDwtIGltcG9ydGFuY2VfdmFsdWVzJHZhcmlhYmxlWzE6aV0NCiAgDQogICMgRml0IHJhbmRvbSBmb3Jlc3QgbW9kZWwNCiAgdHJhaW5fcmVkdWNlZCA8LSB0cmFpbiAlPiUgc2VsZWN0KGhhc19jYXJkLCB2YXJzX3N1YnNldCkNCiAgbW9kZWwgPC0gcmFuZG9tRm9yZXN0KGhhc19jYXJkIH4gLiwgZGF0YSA9IHRyYWluX3JlZHVjZWQsIG1ldGhvZCA9ICJjbGFzcyIpDQogIA0KICAjIE1ha2UgcHJlZGljdGlvbnMgb24gdGVzdCBkYXRhDQogIHByZWRpY3Rpb25zIDwtIHByZWRpY3QobW9kZWwsIHRlc3QsIHR5cGUgPSAicHJvYiIpWywgMl0NCiAgDQogICMgVGhyZXNob2xkIHByZWRpY3Rpb25zDQogIHRocmVzaG9sZCA8LSAwLjUNCiAgcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQogIA0KICAjIENhbGN1bGF0ZSBmMS1zY29yZQ0KICBmMSA8LSBnZXRfbWV0cmljcyhwcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQsICJyYW5kb20gZm9yZXN0IHRlc3QiKSRmMQ0KICANCiAgIyBBcHBlbmQgZjEtc2NvcmUgdG8gdmVjdG9yDQogIGYxX3Njb3JlcyA8LSBjKGYxX3Njb3JlcywgZjEpDQp9DQoNCiMgRmluZCB0aGUgaW5kZXggb2YgdGhlIG1heGltdW0gZjEtc2NvcmUNCmJlc3RfaW5kZXggPC0gd2hpY2gubWF4KGYxX3Njb3JlcykNCg0KIyBQcmludCB0aGUgYmVzdCBmMS1zY29yZSBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgbnVtYmVyIG9mIHZhcmlhYmxlcw0KcHJpbnQocGFzdGUoIkJlc3QgZjEtc2NvcmU6IiwgZjFfc2NvcmVzW2Jlc3RfaW5kZXhdKSkNCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgdmFyaWFibGVzOiIsIGJlc3RfaW5kZXgpKQ0KDQojIFBsb3QgdGhlIGYxLXNjb3Jlcw0KcGxvdCgxOmxlbmd0aChmMV9zY29yZXMpLCBmMV9zY29yZXMsIHR5cGUgPSAibCIsIHhsYWIgPSAiTnVtYmVyIG9mIHZhcmlhYmxlcyIsIHlsYWIgPSAiZjEtc2NvcmUiKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjcpDQojIEZpdCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdG8gdGhlIHRyYWluaW5nIGRhdGENCnJhbmRvbV9mb3Jlc3RfaW1wb3J0YW50X2ZlYXR1cmVzIDwtIHJhbmRvbUZvcmVzdChoYXNfY2FyZCB+IC4sIGRhdGEgPSB0cmFpbiAlPiUgc2VsZWN0KGhhc19jYXJkLCBpbXBvcnRhbmNlX3ZhbHVlcyR2YXJpYWJsZVsxOmJlc3RfaW5kZXhdKSwgbWV0aG9kID0gImNsYXNzIikNCg0KY3ZfbWV0aG9kIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkNCiNyYW5kb21fZm9yZXN0X2ltcG9ydGFudF9mZWF0dXJlcyA8LSB0cmFpbihoYXNfY2FyZCB+IC4sIGRhdGEgPSB0cmFpbiwgbWV0aG9kID0gcmFuZG9tX2ZvcmVzdF9pbXBvcnRhbnRfZmVhdHVyZXMsIHRyQ29udHJvbCA9IGN2X21ldGhvZCkNCg0KcmFuZG9tX2ZvcmVzdF9pbXBvcnRhbnRfZmVhdHVyZXNfZXhwbGFpbmVyIDwtIGV4cGxhaW4ocmFuZG9tX2ZvcmVzdF9pbXBvcnRhbnRfZmVhdHVyZXMsIGRhdGEgPSB0cmFpbikNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IGRhdGEgKHNlY29uZCBjb2x1bW4gaXMgZm9yIHByb2JhYmlsaXR5IG9mIHRydWUpDQpyYW5kb21fZm9yZXN0X2ltcG9ydGFudF9mZWF0dXJlc19wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHJhbmRvbV9mb3Jlc3RfaW1wb3J0YW50X2ZlYXR1cmVzLCB0ZXN0LCB0eXBlID0gInByb2IiKVssIDJdDQoNCiMgVGhyZXNob2xkIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcw0KdGhyZXNob2xkIDwtIDAuNQ0KcmFuZG9tX2ZvcmVzdF9pbXBvcnRhbnRfZmVhdHVyZXNfdGhyZXNob2xkIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQoNCiMgRXZhbHVhdGUgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UNCnJhbmRvbV9mb3Jlc3RfaW1wb3J0YW50X2ZlYXR1cmVzX21ldHJpY3MgPC0gZ2V0X21ldHJpY3MocmFuZG9tX2ZvcmVzdF9pbXBvcnRhbnRfZmVhdHVyZXNfdGhyZXNob2xkLCB0ZXN0JGhhc19jYXJkLCAiUmFuZG9tIEZvcmVzdCAobW9zdCBJbXBvcnRhbnQgRmVhdHVyZXMpIikNCm1ldHJpY3NfYWxsIDwtIGJpbmRfcm93cyhtZXRyaWNzX2FsbCwgcmFuZG9tX2ZvcmVzdF9pbXBvcnRhbnRfZmVhdHVyZXNfbWV0cmljcykNCnJhbmRvbV9mb3Jlc3RfaW1wb3J0YW50X2ZlYXR1cmVzX21ldHJpY3MNCmBgYA0KDQoNCg0KIyBIeXBlcnBhcmFtdGVyIE9wdGltaWVydW5nIGF1ZiBSYW5kb20gRm9yZXN0DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjcpDQojIERlZmluZSB0aGUgaHlwZXJwYXJhbWV0ZXIgZ3JpZA0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZChtdHJ5ID0gYygwLjEsIDEsIDEwLCAxMDApLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cnVsZSA9IGMoImdpbmkiLCAiZXh0cmF0cmVlcyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5ub2RlLnNpemUgPSBjKDEsIDEwLCAxNSwgMjApLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5kZXB0aCA9IHNlcSgxLCAzMCwgNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSBjKDEwMCwgNTAwLCAxMDAwKSkNCg0KIyBEZWZpbmUgdGhlIG1vZGVsIHVzaW5nIHRoZSB0cmFpbigpIGZ1bmN0aW9uDQpyYW5kb21fZm9yZXN0X2h5cGVycGFyYW1ldGVyIDwtIHJhbmRvbUZvcmVzdChoYXNfY2FyZCB+IC4sDQogICAgICAgICAgICAgICBkYXRhID0gdHJhaW4gJT4lIHNlbGVjdChoYXNfY2FyZCwgaW1wb3J0YW5jZV92YWx1ZXMkdmFyaWFibGVbMTo3XSksDQogICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBwYXJhbV9ncmlkLA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKSkNCg0KcmFuZG9tX2ZvcmVzdF9oeXBlcnBhcmFtZXRlcl9leHBsYWluZXIgPC0gZXhwbGFpbihyYW5kb21fZm9yZXN0X2h5cGVycGFyYW1ldGVyLCBkYXRhID0gdHJhaW4pDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhDQpyYW5kb21fZm9yZXN0X2h5cGVycGFyYW1ldGVyX3ByZWRpY3Rpb25zIDwtIHByZWRpY3QocmFuZG9tX2ZvcmVzdF9oeXBlcnBhcmFtZXRlciwgdGVzdCwgdHlwZSA9ICJwcm9iIilbLCAyXQ0KDQojIFRocmVzaG9sZCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMNCnRocmVzaG9sZCA8LSAwLjUNCnJhbmRvbV9mb3Jlc3RfaHlwZXJwYXJhbWV0ZXJfcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGlmZWxzZShyYW5kb21fZm9yZXN0X2h5cGVycGFyYW1ldGVyX3ByZWRpY3Rpb25zID4gdGhyZXNob2xkLCBUUlVFLCBGQUxTRSkNCg0KcHJpbnQocmFuZG9tX2ZvcmVzdF9oeXBlcnBhcmFtZXRlcikNCg0KIyBFdmFsdWF0ZSB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZQ0KZ2V0X21ldHJpY3MocmFuZG9tX2ZvcmVzdF9oeXBlcnBhcmFtZXRlcl9wcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQsICJSYW5kb20gRm9yZXN0IChIeXBlcnBhcmFtdGVyIE9wdGltaXphdGlvbiIpDQpgYGANCg0KDQoNCiMgUmVjdXJzaXZlIEZlYXR1cmUgRWxpbW5hdGlvbg0KDQpFaW5lIE3DtmdsaWNoa2VpdCwgZGllIExlaXN0dW5nIHZvbiBSYW5kb20gRm9yZXN0IHp1IHZlcmJlc3Nlcm4sIGlzdCBkaWUgVmVyd2VuZHVuZyB2b24gUmVjdXJzaXZlIEZlYXR1cmUgRWxpbWluYXRpb24gKFJGRSkuDQoNClJGRSBpc3QgZWluIEZlYXR1cmUgU2VsZWN0aW9uLVZlcmZhaHJlbiwgZGFzIGRhenUgdmVyd2VuZGV0IHdpcmQsIGRpZSB3aWNodGlnc3RlbiBGZWF0dXJlcyAoYWxzbyBkaWVqZW5pZ2VuIE1lcmttYWxlLCBkaWUgZsO8ciBkaWUgVm9yaGVyc2FnZSBhbSB3aWNodGlnc3RlbiBzaW5kKSBhdXN6dXfDpGhsZW4gdW5kIGFsbGUgYW5kZXJlbiB6dSBlbnRmZXJuZW4uIERpZXMgaGF0IG1laHJlcmUgVm9ydGVpbGU6DQoNCkVzIHJlZHV6aWVydCBkaWUgTGF1ZnplaXQgdm9uIFJhbmRvbSBGb3Jlc3QsIGRhIHdlbmlnZXIgRmVhdHVyZXMgdmVyYXJiZWl0ZXQgd2VyZGVuIG3DvHNzZW4uDQpFcyBrYW5uIGRhenUgYmVpdHJhZ2VuLCBPdmVyZml0dGluZyB6dSB2ZXJtZWlkZW4sIGluZGVtIGVzIGlycmVsZXZhbnRlbiBvZGVyIHJlZHVuZGFudGVuIEZlYXR1cmVzIGVudGZlcm50Lg0KRXMga2FubiBkYXp1IGJlaXRyYWdlbiwgZGllIEludGVycHJldGllcmJhcmtlaXQgdm9uIFJhbmRvbSBGb3Jlc3QgenUgdmVyYmVzc2VybiwgZGEgd2ljaHRpZ2VyZSBGZWF0dXJlcyBsZWljaHRlciB6dSB2ZXJzdGVoZW4gc2luZC4NCg0KDQpgYGB7cn0NCmlmIChGQUxTRSkgew0KIyBEZWZpbmUgdGhlIGNvbnRyb2wgb2JqZWN0DQpjb250cm9sIDwtIHJmZUNvbnRyb2woZnVuY3Rpb25zID0gcmZGdW5jcywNCiAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsDQogICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cyA9IDUpDQoNCiMgUGVyZm9ybSBSRkUNCnJmZSA8LSByZmUoeCA9IHRyYWluICU+JSBzZWxlY3QoLWhhc19jYXJkKSwgeSA9IHRyYWluJGhhc19jYXJkLA0KICAgICAgICAgICAgIHNpemVzID0gYygxOjE5LCBzZXEoZnJvbSA9IDIwLCB0byA9IG5jb2wodHJhaW4pLCBieSA9IDEwKSksDQogICAgICAgICAgICAgcmZlQ29udHJvbCA9IGNvbnRyb2wpDQoNCiMgRXh0cmFjdCB0aGUgc2VsZWN0ZWQgZmVhdHVyZXMNCnNlbGVjdGVkX2ZlYXR1cmVzIDwtIHJmZSRvcHRWYXJpYWJsZXMNCg0KcHJpbnQocmZlKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KaWYgKEZBTFNFKSB7DQojIEZpdCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdXNpbmcgdGhlIHNlbGVjdGVkIGZlYXR1cmVzDQpyYW5kb21fZm9yZXN0X3JmZSA8LSByYW5kb21Gb3Jlc3QoaGFzX2NhcmQgfiAuLCBkYXRhID0gdHJhaW5bYyhzZWxlY3RlZF9mZWF0dXJlcywgImhhc19jYXJkIildLCBtZXRob2QgPSAiY2xhc3MiKQ0KDQpjdl9tZXRob2QgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0KcmFuZG9tX2ZvcmVzdF9yZmUgPC0gdHJhaW4oaGFzX2NhcmQgfiAuLCBkYXRhID0gdHJhaW4sIG1ldGhvZCA9IHJhbmRvbV9mb3Jlc3RfcmZlLCB0ckNvbnRyb2wgPSBjdl9tZXRob2QpDQoNCnJhbmRvbV9mb3Jlc3RfcmZlX2V4cGxhaW5lciA8LSBleHBsYWluKHJhbmRvbV9mb3Jlc3RfcmZlLCBkYXRhID0gdHJhaW4pDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhDQpyYW5kb21fZm9yZXN0X3JmZV9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHJhbmRvbV9mb3Jlc3RfcmZlLCB0ZXN0LCB0eXBlID0gInByb2IiKVssIDJdDQoNCiMgVGhyZXNob2xkIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcw0KdGhyZXNob2xkIDwtIDAuNQ0KcmFuZG9tX2ZvcmVzdF9yZmVfcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGlmZWxzZShyYW5kb21fZm9yZXN0X3JmZV9wcmVkaWN0aW9ucyA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQoNCiMgRXZhbHVhdGUgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UNCnJhbmRvbV9mb3Jlc3RfcmZlX21ldHJpY3MgPC0gZ2V0X21ldHJpY3MocmFuZG9tX2ZvcmVzdF9yZmVfcHJlZGljdGlvbnNfdGhyZXNob2xkLCB0ZXN0JGhhc19jYXJkLCAiUmFuZG9tIEZvcmVzdCAoUkZFKSIpDQptZXRyaWNzX2FsbCA8LSBiaW5kX3Jvd3MobWV0cmljc19hbGwsIHJhbmRvbV9mb3Jlc3RfcmZlX21ldHJpY3MpDQpyYW5kb21fZm9yZXN0X3JmZV9tZXRyaWNzDQp9DQpgYGANCg0KYGBge3J9DQptZXRyaWNzX2FsbA0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KG1ldHJpY3NfYWxsLCBhZXMoeCA9IG1vZGVsLCB5ID0gZjEpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGluZShhZXMoZ3JvdXAgPSAxKSkgKyB5bGltKDAuNSwxKQ0KDQpgYGANCg0KDQpgYGB7cn0NCiMgTmV4dCwgY3JlYXRlIGEgcGxvdCB1c2luZyBnZ3Bsb3QoKSBhbmQgc3BlY2lmeSB0aGUgZGF0YWZyYW1lIGFzIHRoZSBmaXJzdCBhcmd1bWVudA0KZ2dwbG90KGRhdGEgPSBtZXRyaWNzX2FsbCwgYWVzKHggPSBtb2RlbCkpICsNCg0KIyBVc2UgZ2VvbV9saW5lKCkgdG8gY3JlYXRlIGEgbGluZSBwbG90LCBhbmQgbWFwIHRoZSBGMSBtZXRyaWMgdG8gdGhlIHktYXhpcyB3aXRoIGFlcygpDQpnZW9tX2xpbmUoYWVzKHkgPSBmMSkpDQoNCiMgRmluYWxseSwgdXNlIGxhYnMoKSB0byBsYWJlbCB0aGUgeC0gYW5kIHktYXhlcyBhbmQgZ2l2ZSB0aGUgcGxvdCBhIHRpdGxlDQpsYWJzKHggPSAiTW9kZWwiLCB5ID0gIkYxIHZhbHVlIiwgdGl0bGUgPSAiTW9kZWwgcGVyZm9ybWFuY2UgKEYxKSIpDQpgYGANCg0KDQpgYGB7cn0NCiMgTmV4dCwgY3JlYXRlIGEgcGxvdCB1c2luZyBnZ3Bsb3QoKSBhbmQgc3BlY2lmeSB0aGUgZGF0YWZyYW1lIGFzIHRoZSBmaXJzdCBhcmd1bWVudA0KZ2dwbG90KGRhdGEgPSBtZXRyaWNzX2FsbCwgYWVzKHggPSBtb2RlbCkpICsNCg0KIyBVc2UgZ2VvbV9saW5lKCkgdG8gY3JlYXRlIGEgbGluZSBwbG90LCBhbmQgbWFwIHRoZSBkaWZmZXJlbnQgbWV0cmljcyB0byB0aGUgeS1heGlzIGFuZCB0aGUgY29sb3IgYWVzdGhldGljIHdpdGggYWVzKCkNCmdlb21fbGluZShhZXMoeSA9IGFjY3VyYWN5LCBjb2xvciA9ICJBY2N1cmFjeSIpKSArDQpnZW9tX2xpbmUoYWVzKHkgPSBrYXBwYSwgY29sb3IgPSAiS2FwcGEiKSkgKw0KZ2VvbV9saW5lKGFlcyh5ID0gbWF0dGhld3MsIGNvbG9yID0gIk1hdHRoZXdzIikpICsNCmdlb21fbGluZShhZXMoeSA9IHByZWNpc2lvbiwgY29sb3IgPSAiUHJlY2lzaW9uIikpICsNCmdlb21fbGluZShhZXMoeSA9IHJlY2FsbCwgY29sb3IgPSAiUmVjYWxsIikpICsNCmdlb21fbGluZShhZXMoeSA9IGYxLCBjb2xvciA9ICJGMSIpKQ0KDQojIFVzZSBzY2FsZV9jb2xvcl9tYW51YWwoKSB0byBzcGVjaWZ5IHRoZSBjb2xvcnMgZm9yIGVhY2ggbWV0cmljDQpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiQWNjdXJhY3kiID0gImJsdWUiLCAiS2FwcGEiID0gInJlZCIsICJNYXR0aGV3cyIgPSAiZ3JlZW4iLCAiUHJlY2lzaW9uIiA9ICJwdXJwbGUiLCAiUmVjYWxsIiA9ICJvcmFuZ2UiLCAiRjEiID0gInBpbmsiKSkNCg0KIyBGaW5hbGx5LCB1c2UgbGFicygpIHRvIGxhYmVsIHRoZSB4LSBhbmQgeS1heGVzIGFuZCBnaXZlIHRoZSBwbG90IGEgdGl0bGUNCmxhYnMoeCA9ICJNb2RlbCIsIHkgPSAiTWV0cmljIHZhbHVlIiwgdGl0bGUgPSAiTW9kZWwgcGVyZm9ybWFuY2UiKQ0KYGBgDQoNCg0KYGBge3J9DQojY29tcGFyaXNvbiA8LSBjb21wYXJlKHJlZ3Jlc3Npb25fYmFzZWxpbmVfZXhwbGFpbmVyLCByZWdyZXNzaW9uX2FsbF9leHBsYWluZXIsIGRlY2lzaW9uX3RyZWVfZXhwbGFpbmVyLCByYW5kb21fZm9yZXN0X2V4cGxhaW5lcikNCg0KdmlwX2Jhc2VsaW5lIDwtIHZhcmlhYmxlX2ltcG9ydGFuY2UocmVncmVzc2lvbl9iYXNlbGluZV9leHBsYWluZXIsIG5fc2FtcGxlID0gLTEsIGxvc3NfZnVuY3Rpb24gPSBsb3NzX3Jvb3RfbWVhbl9zcXVhcmUpIA0KdmlwX3JlZ3Jlc3Npb25fYWxsICA8LSB2YXJpYWJsZV9pbXBvcnRhbmNlKHJlZ3Jlc3Npb25fYWxsX2V4cGxhaW5lciwgbl9zYW1wbGUgPSAtMSwgbG9zc19mdW5jdGlvbiA9IGxvc3Nfcm9vdF9tZWFuX3NxdWFyZSkNCnZpcF9kZWNpc2lvbl90cmVlIDwtIHZhcmlhYmxlX2ltcG9ydGFuY2UoZGVjaXNpb25fdHJlZV9leHBsYWluZXIsIG5fc2FtcGxlID0gLTEsIGxvc3NfZnVuY3Rpb24gPSBsb3NzX3Jvb3RfbWVhbl9zcXVhcmUpDQp2aXBfcmFuZG9tX2ZvcmVzdCA8LSB2YXJpYWJsZV9pbXBvcnRhbmNlKHJhbmRvbV9mb3Jlc3RfZXhwbGFpbmVyLCBuX3NhbXBsZSA9IC0xLCBsb3NzX2Z1bmN0aW9uID0gbG9zc19yb290X21lYW5fc3F1YXJlKQ0KDQpwbG90KHZpcF9iYXNlbGluZSwgdmlwX3JlZ3Jlc3Npb25fYWxsLCB2aXBfZGVjaXNpb25fdHJlZSwgdmlwX3JhbmRvbV9mb3Jlc3QsIG1heF92YXJzID0gMTApDQpgYGANCg0KDQpgYGB7cn0NCmZlaGxlcg0KYGBgDQoNCg0KDQoNCiMjIEh5cGVycGFyYW1ldGVyb3B0aW1pZXJ1bmcNCg0KYGBge3J9DQppZiAoRkFMU0UpIHsNCiMgU2V0IHRoZSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkNCnNldC5zZWVkKDI3KQ0KDQp0cmFpbiRoYXNfY2FyZCA8LSBhcy5udW1lcmljKHRyYWluJGhhc19jYXJkKQ0KdGVzdCRoYXNfY2FyZCA8LSBhcy5udW1lcmljKHRlc3QkaGFzX2NhcmQpDQoNCiMgQ29udmVydCB0aGUgZGF0YSB0byBhIG1hdHJpeA0KbWF0IDwtIGFzLm1hdHJpeCh0cmFpbiAlPiUgc2VsZWN0KGhhc19jYXJkLCBpbXBvcnRhbmNlX3ZhbHVlcyR2YXJpYWJsZVsxOjddKSkNCg0KIyBTcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMNCnRyYWluX2luZCA8LSBzYW1wbGUoMTpucm93KG1hdCksIG5yb3cobWF0KSAqIDAuOCkNCnRyYWluX21hdCA8LSBtYXRbdHJhaW5faW5kLCBdDQp0ZXN0X21hdCA8LSBtYXRbLXRyYWluX2luZCwgXQ0KDQojIFNwbGl0IHRoZSBsYWJlbHMgYW5kIHByZWRpY3RvcnMNCnRyYWluX2xhYmVscyA8LSB0cmFpbl9tYXRbLCAxXQ0KdHJhaW5fcHJlZHMgPC0gdHJhaW5fbWF0WywgLTFdDQp0ZXN0X2xhYmVscyA8LSB0ZXN0X21hdFssIDFdDQp0ZXN0X3ByZWRzIDwtIHRlc3RfbWF0WywgLTFdDQoNCnRyYWluX2xhYmVscyA8LSBhcy5mYWN0b3IodHJhaW5fbGFiZWxzKQ0KdGVzdF9sYWJlbHMgPC0gYXMuZmFjdG9yKHRlc3RfbGFiZWxzKQ0KDQojIERlZmluZSB0aGUgaHlwZXJwYXJhbWV0ZXIgZ3JpZA0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZChucm91bmRzID0gYygxMDAwLCAyMDAwLCA1MDAwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBldGEgPSBjKDAuMSwgMC4yLCAwLjMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9kZXB0aCA9IGMoMywgNSwgNywgMTApLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGdhbW1hID0gYygwLCAwLjEsIDAuMiwgMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC41LCAwLjgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IGMoMC41LCAwLjgsIDEpKQ0KDQojIFRyYWluIHRoZSBtb2RlbCB1c2luZyBjYXJldCdzIHRyYWluKCkgZnVuY3Rpb24NCnhnX2Jvb3N0X2h5cGVycGFyYW1ldGVyIDwtIHRyYWluKHggPSB0cmFpbl9wcmVkcywgeSA9IHRyYWluX2xhYmVscywgbWV0aG9kID0gInhnYlRyZWUiLCB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLCB0dW5lR3JpZCA9IHBhcmFtX2dyaWQsIG1ldHJpYyA9ICJGIiwgdmVyYm9zZSA9IEZBTFNFLCB2ZXJib3NpdHkgPSAwKQ0KDQoNCnByaW50KG1vZGVsKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YQ0KeGdfYm9vc3RfaHlwZXJwYXJhbWV0ZXJfcHJlZGljdGlvbnMgPC0gcHJlZGljdCh4Z19ib29zdF9oeXBlcnBhcmFtZXRlciwgdGVzdF9wcmVkcykNCg0KIyBUaHJlc2hvbGQgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzDQp0aHJlc2hvbGQgPC0gMC41DQp4Z19ib29zdF9oeXBlcnBhcmFtZXRlcl9wcmVkaWN0aW9uc190aHJlc2hvbGQgPC0gaWZlbHNlKHhnX2Jvb3N0X2h5cGVycGFyYW1ldGVyX3ByZWRpY3Rpb25zID4gdGhyZXNob2xkLCBUUlVFLCBGQUxTRSkNCg0KIyBFdmFsdWF0ZSB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZQ0KeGdfYm9vc3RfaHlwZXJwYXJhbXRlcl9tZXRyaWNzIDwtIGdldF9tZXRyaWNzKHhnX2Jvb3N0X2h5cGVycGFyYW1ldGVyX3ByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdF9sYWJlbHMpDQptZXRyaWNzX2FsbCA8LSBiaW5kX3Jvd3MobWV0cmljc19hbGwsIHhnX2Jvb3N0X2h5cGVycGFyYW10ZXJfbWV0cmljcykNCnhnX2Jvb3N0X2h5cGVycGFyYW10ZXJfbWV0cmljcw0KfQ0KYGBgDQoNCg0KIyDDnGJlcnNpY2h0IMO8YmVyIGFsbGUgTW9kZWxsZQ0KDQpgYGB7cn0NCm1ldHJpY3NfYWxsDQpgYGANCg0KDQojIEZheml0DQoNCiMjIE1laHJ3ZXJ0IGRlcyBNb2RlbGxzIGluIGRlciBQcmF4aXMNCg0KQmVpIHVuc2VyZW0gTW9kZWwgaGFuZGVsdCBlcyBzaWNoIHVtIGVpbiBQcm9kdWN0IEFmZmluaXR5IE1vZGVsLiBFcyBzb2xsIHZvcmF1c3NhZ2VuLCBvYiBlaW5lIFBlcnNvbiAoaGllciBlaW5lIEt1bmQ6aW4gZGVyIEJhbmspIGVpbiBiZXN0aW1tdGVzIFByb2R1a3Qga2F1ZnQgKGhpZXIgZWluZSBLcmVkaXRrYXJ0ZSkgb2RlciBuaWNodC4gQXVzc2VyZGVtIGdpYnQgdW5zZXIgTW9kZWxsIGF1Y2ggYW4sIG1pdCB3ZWxjaGVyIFdhaHJzY2hlaW5saWNoa2VpdCBzaWUgZGllc2Uga2F1ZnQuIERpZXMga8O2bm50ZSB2b24gZ3Jvc3NlbSBNZWhyd2VydCBmw7xyIGVpbmUgQmFuayBzZWluLCBkYSBzaWUgYW5oYW5kIGRlcyBNb2RlbGxzIGRpZSBNYXJrZXRpbmctU3RyYXRlZ2llIG9wdGltaWVyZW4gdW5kIGlocmUgUmVzc291cmNlbiBnZXppZWx0ZXIgZWluc2V0emVuIGvDtm5uZW4uDQoNCkVzIGthbm4gQmFua2VuIGhlbGZlbiwgaWhyZSBNYXJrZXRpbmctQmVtw7xodW5nZW4gYmVzc2VyIHp1IHNlZ21lbnRpZXJlbiwgaW5kZW0gZXMgemVpZ3QsIHdlciB3YWhyc2NoZWlubGljaCBhbiBlaW5lciBLcmVkaXRrYXJ0ZSBpbnRlcmVzc2llcnQgaXN0IHVuZCB3ZXIgbmljaHQuIERpZXMga2FubiBkYXp1IGJlaXRyYWdlbiwgZGllIENvbnZlcnNpb24tUmF0ZSB6dSB2ZXJiZXNzZXJuIHVuZCBkaWUgS29zdGVuIGbDvHIgTWFya2V0aW5nLUFrdGlvbmVuIHp1IHNlbmtlbi4gDQoNCkVpbiB3ZWl0ZXJlciBWb3J0ZWlsLCBkZW4gZWluIFByb2R1Y3QgQWZmaW5pdHkgTW9kZWwgYmlldGVuIGthbm4sIGlzdCwgZGFzcyBlcyBCYW5rZW4gaGVsZmVuIGthbm4sIGlocmUgS3VuZGVuYmV6aWVodW5nZW4genUgdmVyYmVzc2VybiwgaW5kZW0gZXMgaWhuZW4gZXJtw7ZnbGljaHQsIHBlcnNvbmFsaXNpZXJ0ZSBBbmdlYm90ZSB1bmQgU2VydmljZXMgYW56dWJpZXRlbi4NCg0KV2VubiBkYXMgTW9kZWxsIHZvcmhlcnNhZ3QsIHdlciB3YWhyc2NoZWlubGljaCBlaW5lIEtyZWRpdGthcnRlIGthdWZlbiB3aXJkLCBrw7ZubmVuIEJhbmtlbiBpaHJlbiBLdW5kZW4gcGVyc29uYWxpc2llcnRlIEFuZ2Vib3RlIHVuZCBTZXJ2aWNlcyBhbmJpZXRlbiwgZGllIGF1ZiBpaHJlIGluZGl2aWR1ZWxsZW4gQmVkw7xyZm5pc3NlIHVuZCBWb3JsaWViZW4gYWJnZXN0aW1tdCBzaW5kLiBEaWVzIGthbm4gZGF6dSBiZWl0cmFnZW4sIGRpZSBLdW5kZW56dWZyaWVkZW5oZWl0IHp1IGVyaMO2aGVuIHVuZCBkaWUgS3VuZGVuYmluZHVuZyB6dSBzdMOkcmtlbi4gRGFmw7xyIG3DvHNzdGUgZGFzIE1vZGVsbCBhYmVyIG5vY2ggZXJ3ZWl0ZXJ0IHdlcmRlbi4NCg0KRWluIHBlcnNvbmFsaXNpZXJ0ZXMgQW5nZWJvdCBrw7ZubnRlIGJlaXNwaWVsc3dlaXNlIGVpbmUgS3JlZGl0a2FydGUgc2VpbiwgZGllIHNwZXppZWxsIGF1ZiBkaWUgQmVkw7xyZm5pc3NlIHVuZCBWb3JsaWViZW4gZWluZXMgS3VuZGVuIGFiZ2VzdGltbXQgaXN0LCB6LkIuIG1pdCBlaW5lbSBob2hlbiBSw7xja3phaGx1bmdzcHJvemVudHNhdHogZsO8ciBiZXN0aW1tdGUgS2F0ZWdvcmllbiB2b24gRWlua8OkdWZlbiBvZGVyIG1pdCBSZWlzZXZlcnNpY2hlcnVuZ2VuIHVuZCBhbmRlcmVuIFZvcnRlaWxlbiwgZGllIGbDvHIgZGVuIEt1bmRlbiBiZXNvbmRlcnMgd2ljaHRpZyBzaW5kLg0KDQpJbmRlbSBCYW5rZW4gaWhyZW4gS3VuZGVuIHBlcnNvbmFsaXNpZXJ0ZSBBbmdlYm90ZSB1bmQgU2VydmljZXMgYW5iaWV0ZW4sIGvDtm5uZW4gc2llIGlocmUgQmV6aWVodW5nZW4genUgaWhuZW4gc3TDpHJrZW4gdW5kIGlocmUgWnVmcmllZGVuaGVpdCBlcmjDtmhlbi4NCg0K